From a0e3d7d1923a09f9855ad4f6d4dadd34e0708885 Mon Sep 17 00:00:00 2001 From: Ishaan Gupta Date: Mon, 13 Apr 2026 02:52:41 +0530 Subject: [PATCH 1/9] perf(middleware): await background task cancellation on timeout (#786) --- packages/openai-sdk-python/pyproject.toml | 2 +- .../src/supermemory_openai/middleware.py | 9 ++++++--- packages/openai-sdk-python/uv.lock | 2 +- 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/packages/openai-sdk-python/pyproject.toml b/packages/openai-sdk-python/pyproject.toml index b47445012..c808d79f8 100644 --- a/packages/openai-sdk-python/pyproject.toml +++ b/packages/openai-sdk-python/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "hatchling.build" [project] name = "supermemory-openai-sdk" -version = "1.0.2" +version = "1.0.3" description = "Memory tools for OpenAI function calling with supermemory" readme = "README.md" license = "MIT" diff --git a/packages/openai-sdk-python/src/supermemory_openai/middleware.py b/packages/openai-sdk-python/src/supermemory_openai/middleware.py index 4f6dc8ec0..12100065c 100644 --- a/packages/openai-sdk-python/src/supermemory_openai/middleware.py +++ b/packages/openai-sdk-python/src/supermemory_openai/middleware.py @@ -554,9 +554,12 @@ async def wait_for_background_tasks(self, timeout: Optional[float] = 10.0) -> No f"Background tasks did not complete within {timeout}s timeout" ) # Cancel remaining tasks - for task in self._background_tasks: - if not task.done(): - task.cancel() + tasks_to_cancel = [task for task in self._background_tasks if not task.done()] + for task in tasks_to_cancel: + task.cancel() + + if tasks_to_cancel: + await asyncio.gather(*tasks_to_cancel, return_exceptions=True) raise def cancel_background_tasks(self) -> None: diff --git a/packages/openai-sdk-python/uv.lock b/packages/openai-sdk-python/uv.lock index 7790fd44a..26688c59f 100644 --- a/packages/openai-sdk-python/uv.lock +++ b/packages/openai-sdk-python/uv.lock @@ -2427,7 +2427,7 @@ wheels = [ [[package]] name = "supermemory-openai-sdk" -version = "1.0.2" +version = "1.0.3" source = { editable = "." } dependencies = [ { name = "openai" }, From 7fd36eaebcf10b3e4eab5409c43773bf26f49364 Mon Sep 17 00:00:00 2001 From: Ishaan Gupta Date: Mon, 13 Apr 2026 03:11:48 +0530 Subject: [PATCH 2/9] Optimize onboarding chat document creation with Promise.all (#822) --- .../onboarding/setup/chat-sidebar.tsx | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/apps/web/components/onboarding/setup/chat-sidebar.tsx b/apps/web/components/onboarding/setup/chat-sidebar.tsx index 501c8fd61..022135d68 100644 --- a/apps/web/components/onboarding/setup/chat-sidebar.tsx +++ b/apps/web/components/onboarding/setup/chat-sidebar.tsx @@ -381,11 +381,9 @@ export function ChatSidebar({ formData }: ChatSidebarProps) { setIsLoading(true) try { - const documentIds: string[] = [] - - for (const draft of draftDocs) { + const promises = draftDocs.map(async (draft) => { if (draft.kind === "x_research" && xResearchStatus !== "correct") { - continue + return null } try { @@ -397,13 +395,17 @@ export function ChatSidebar({ formData }: ChatSidebarProps) { }, }) - if (docResponse.data?.id) { - documentIds.push(docResponse.data.id) - } + return docResponse.data?.id } catch (error) { console.warn("Error creating document:", error) + return null } - } + }) + + const results = await Promise.all(promises) + const documentIds = results.filter( + (id): id is string => id !== null && id !== undefined, + ) if (documentIds.length > 0) { await pollForMemories(documentIds) From c7b841798f69e00cc72c8d3ca392c274ad3753cc Mon Sep 17 00:00:00 2001 From: Ishaan Gupta Date: Mon, 13 Apr 2026 03:15:29 +0530 Subject: [PATCH 3/9] perf: Execute Claude memory tool calls concurrently (#824) --- .../tools/test/claude-memory-real-example.ts | 54 ++++++++++--------- 1 file changed, 28 insertions(+), 26 deletions(-) diff --git a/packages/tools/test/claude-memory-real-example.ts b/packages/tools/test/claude-memory-real-example.ts index 1124faa8e..bb6070d48 100644 --- a/packages/tools/test/claude-memory-real-example.ts +++ b/packages/tools/test/claude-memory-real-example.ts @@ -119,25 +119,25 @@ export async function realClaudeMemoryExample() { const toolResults = [] if (responseData.content) { - for (const block of responseData.content) { - if (block.type === "tool_use" && block.name === "memory") { - console.log("\\n🔧 Processing memory tool call:") + const memoryToolCalls = responseData.content.filter( + (block: any): block is { type: 'tool_use'; id: string; name: 'memory'; input: { command: MemoryCommand; path: string } } => + block.type === "tool_use" && block.name === "memory", + ) + + const results = await Promise.all( + memoryToolCalls.map((block: any) => { + console.log("\n🔧 Processing memory tool call:") console.log(`Command: ${block.input.command}`) console.log(`Path: ${block.input.path}`) - // Handle the memory tool call - const toolResult = await handleClaudeMemoryToolCall( - block, - SUPERMEMORY_API_KEY, - { - projectId: "python-scraper-help", - memoryContainerTag: "claude_memory_debug", - }, - ) - - toolResults.push(toolResult) - } - } + return handleClaudeMemoryToolCall(block, SUPERMEMORY_API_KEY, { + projectId: "python-scraper-help", + memoryContainerTag: "claude_memory_debug", + }) + }), + ) + + toolResults.push(...results) } // Step 3: Send tool results back to Claude if there were any @@ -196,16 +196,18 @@ export async function processClaudeResponse( const toolResults = [] if (claudeResponseData.content) { - for (const block of claudeResponseData.content) { - if (block.type === "tool_use" && block.name === "memory") { - const toolResult = await handleClaudeMemoryToolCall( - block, - supermemoryApiKey, - config, - ) - toolResults.push(toolResult) - } - } + const memoryToolCalls = claudeResponseData.content.filter( + (block: any): block is { type: 'tool_use'; id: string; name: 'memory'; input: { command: MemoryCommand; path: string } } => + block.type === "tool_use" && block.name === "memory", + ) + + const results = await Promise.all( + memoryToolCalls.map((block: any) => + handleClaudeMemoryToolCall(block, supermemoryApiKey, config), + ), + ) + + toolResults.push(...results) } return toolResults From 1c230b66b4aa68439377129047f549ab04d7583b Mon Sep 17 00:00:00 2001 From: Vedant Mahajan Date: Mon, 13 Apr 2026 03:26:22 +0530 Subject: [PATCH 4/9] perf: deduplicate OG data fetches across document cards (#831) --- apps/web/components/memories-grid.tsx | 87 ++++++++++++++++++++------- 1 file changed, 66 insertions(+), 21 deletions(-) diff --git a/apps/web/components/memories-grid.tsx b/apps/web/components/memories-grid.tsx index a07d608a3..66ad36e37 100644 --- a/apps/web/components/memories-grid.tsx +++ b/apps/web/components/memories-grid.tsx @@ -76,6 +76,48 @@ type OgData = { image?: string } +const ogCache = new Map() +const ogInflight = new Map>() +const ogFailures = new Map() +const OG_FAILURE_TTL = 30_000 + +function fetchOgData(url: string): Promise { + const cached = ogCache.get(url) + if (cached) return Promise.resolve(cached) + + const failedAt = ogFailures.get(url) + if (failedAt && Date.now() - failedAt < OG_FAILURE_TTL) { + return Promise.resolve(null) + } + + const inflight = ogInflight.get(url) + if (inflight) return inflight + + const promise = fetch(`/api/og?url=${encodeURIComponent(url)}`) + .then((res) => { + if (!res.ok) throw new Error("Failed") + return res.json() + }) + .then((data) => { + const result: OgData = { title: data?.title, image: data?.image } + if (!result.title && !result.image) { + throw new Error("Empty metadata") + } + ogCache.set(url, result) + ogInflight.delete(url) + ogFailures.delete(url) + return result + }) + .catch(() => { + ogInflight.delete(url) + ogFailures.set(url, Date.now()) + return null + }) + + ogInflight.set(url, promise) + return promise +} + const PAGE_SIZE = 100 const MAX_TOTAL = 1000 @@ -682,7 +724,6 @@ const DocumentCard = memo( const [rotation, setRotation] = useState({ rotateX: 0, rotateY: 0 }) const cardRef = useRef(null) const [ogData, setOgData] = useState(null) - const [isLoadingOg, setIsLoadingOg] = useState(false) const ogImage = (document as DocumentWithMemories & { ogImage?: string }) .ogImage @@ -699,27 +740,31 @@ const DocumentCard = memo( const hideURL = document.url?.includes("docs.googleapis.com") useEffect(() => { - if (needsOgData && !ogData && !isLoadingOg && document.url) { - setIsLoadingOg(true) - fetch(`/api/og?url=${encodeURIComponent(document.url)}`) - .then((res) => { - if (!res.ok) throw new Error("Failed") - return res.json() - }) - .then((data) => { - setOgData({ - title: data?.title, - image: data?.image, - }) - }) - .catch(() => { - setOgData({}) - }) - .finally(() => { - setIsLoadingOg(false) - }) + if (!needsOgData || ogData || !document.url) return + + let timeoutId: ReturnType + let mounted = true + + const attemptFetch = () => { + if (!mounted || !document.url) return + fetchOgData(document.url).then((data) => { + if (!mounted) return + if (data) { + setOgData(data) + } else { + // Retry when the global TTL expires + timeoutId = setTimeout(attemptFetch, 30_000) + } + }) + } + + attemptFetch() + + return () => { + mounted = false + clearTimeout(timeoutId) } - }, [needsOgData, ogData, isLoadingOg, document.url]) + }, [needsOgData, ogData, document.url]) useEffect(() => { if (isSelectionMode) setRotation({ rotateX: 0, rotateY: 0 }) From cfd587ed2a385b30bd48409b4dcc1fb89416d9b4 Mon Sep 17 00:00:00 2001 From: Vedant Mahajan Date: Mon, 13 Apr 2026 03:49:34 +0530 Subject: [PATCH 5/9] perf: scope message equality check to content+parts instead of full object (#842) --- apps/web/stores/chat.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/web/stores/chat.ts b/apps/web/stores/chat.ts index 7df867b02..456217b7f 100644 --- a/apps/web/stores/chat.ts +++ b/apps/web/stores/chat.ts @@ -27,9 +27,9 @@ export function areUIMessageArraysEqual( return false } - // Compare the entire message using JSON serialization as a fallback - // This handles all properties including parts, toolInvocations, etc. - if (JSON.stringify(msgA) !== JSON.stringify(msgB)) { + if (msgA.content !== msgB.content) return false + + if (JSON.stringify(msgA.parts) !== JSON.stringify(msgB.parts)) { return false } } From 13bee90bd8be2bae9bcff335c046f1d5e11891d1 Mon Sep 17 00:00:00 2001 From: Ishaan Gupta Date: Mon, 13 Apr 2026 03:57:05 +0530 Subject: [PATCH 6/9] perf(ui): align React Query cache between memory grid and graph (#845) --- apps/web/components/memory-graph/hooks/use-graph-api.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/web/components/memory-graph/hooks/use-graph-api.ts b/apps/web/components/memory-graph/hooks/use-graph-api.ts index 3c3dd5d00..94c08e331 100644 --- a/apps/web/components/memory-graph/hooks/use-graph-api.ts +++ b/apps/web/components/memory-graph/hooks/use-graph-api.ts @@ -104,7 +104,7 @@ export function useGraphApi(options: UseGraphApiOptions = {}) { hasNextPage, fetchNextPage, } = useInfiniteQuery({ - queryKey: ["graph-documents", containerTags?.join(",")], + queryKey: ["documents-with-memories", containerTags, []], initialPageParam: 1, queryFn: async ({ pageParam }) => { const response = await $fetch("@post/documents/documents", { @@ -128,7 +128,7 @@ export function useGraphApi(options: UseGraphApiOptions = {}) { const { currentPage, totalPages } = lastPage.pagination return currentPage < totalPages ? currentPage + 1 : undefined }, - staleTime: 30 * 1000, + staleTime: 5 * 60 * 1000, enabled, }) From 5051658b853d834df6dbf5084c116b1a8d3820bd Mon Sep 17 00:00:00 2001 From: Ishaan Gupta Date: Mon, 13 Apr 2026 04:08:07 +0530 Subject: [PATCH 7/9] perf(ui): convert message pairing to O(n) forward pass in chain-of-thought (#846) --- .../chat/input/chain-of-thought.tsx | 22 ++++++++++++------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/apps/web/components/chat/input/chain-of-thought.tsx b/apps/web/components/chat/input/chain-of-thought.tsx index 9ad3d0595..e15a5daad 100644 --- a/apps/web/components/chat/input/chain-of-thought.tsx +++ b/apps/web/components/chat/input/chain-of-thought.tsx @@ -23,18 +23,24 @@ export function ChainOfThought({ messages }: { messages: UIMessage[] }) { agentMessage?: UIMessage }> = [] + let lastUserPair: { + userMessage: UIMessage + agentMessage?: UIMessage + } | null = null + for (let i = 0; i < messages.length; i++) { const message = messages[i] if (!message) continue + if (message.role === "user") { - // Find the next assistant message after this user message - const agentMessage = messages - .slice(i + 1) - .find((msg) => msg.role === "assistant") - messagePairs.push({ - userMessage: message, - agentMessage, - }) + lastUserPair = { userMessage: message } + messagePairs.push(lastUserPair) + } else if ( + message.role === "assistant" && + lastUserPair && + !lastUserPair.agentMessage + ) { + lastUserPair.agentMessage = message } } From 5cadf5c17073a431ed9d7d321f338896afb6f8bc Mon Sep 17 00:00:00 2001 From: Vedant Mahajan Date: Mon, 13 Apr 2026 04:15:58 +0530 Subject: [PATCH 8/9] perf: wrap list/grid components in React.memo to skip unnecessary re-renders (#847) --- apps/web/components/chat/message/user-message.tsx | 5 +++-- apps/web/components/document-cards/file-preview.tsx | 10 +++++++--- apps/web/components/document-cards/website-preview.tsx | 6 +++--- apps/web/components/document-cards/youtube-preview.tsx | 5 +++-- 4 files changed, 16 insertions(+), 10 deletions(-) diff --git a/apps/web/components/chat/message/user-message.tsx b/apps/web/components/chat/message/user-message.tsx index 8e5d8c5b4..0427f348d 100644 --- a/apps/web/components/chat/message/user-message.tsx +++ b/apps/web/components/chat/message/user-message.tsx @@ -1,5 +1,6 @@ "use client" +import { memo } from "react" import { Copy, Check } from "lucide-react" import type { UIMessage } from "@ai-sdk/react" @@ -9,7 +10,7 @@ interface UserMessageProps { onCopy: (messageId: string, text: string) => void } -export function UserMessage({ +export const UserMessage = memo(function UserMessage({ message, copiedMessageId, onCopy, @@ -38,4 +39,4 @@ export function UserMessage({ ) -} +}) diff --git a/apps/web/components/document-cards/file-preview.tsx b/apps/web/components/document-cards/file-preview.tsx index e5510f935..cd10bcd83 100644 --- a/apps/web/components/document-cards/file-preview.tsx +++ b/apps/web/components/document-cards/file-preview.tsx @@ -1,6 +1,6 @@ "use client" -import { useState } from "react" +import { memo, useState } from "react" import type { DocumentsWithMemoriesResponseSchema } from "@repo/validation/api" import type { z } from "zod" import { dmSansClassName } from "@/lib/fonts" @@ -43,7 +43,11 @@ function getFileTypeInfo(document: DocumentWithMemories): { } } -export function FilePreview({ document }: { document: DocumentWithMemories }) { +export const FilePreview = memo(function FilePreview({ + document, +}: { + document: DocumentWithMemories +}) { const [imageError, setImageError] = useState(false) const { extension, color } = getFileTypeInfo(document) @@ -107,4 +111,4 @@ export function FilePreview({ document }: { document: DocumentWithMemories }) { )} ) -} +}) diff --git a/apps/web/components/document-cards/website-preview.tsx b/apps/web/components/document-cards/website-preview.tsx index 21ae9de22..067004fc7 100644 --- a/apps/web/components/document-cards/website-preview.tsx +++ b/apps/web/components/document-cards/website-preview.tsx @@ -1,6 +1,6 @@ "use client" -import { useState } from "react" +import { memo, useState } from "react" import type { DocumentsWithMemoriesResponseSchema } from "@repo/validation/api" import type { z } from "zod" @@ -12,7 +12,7 @@ type OgData = { image?: string } -export function WebsitePreview({ +export const WebsitePreview = memo(function WebsitePreview({ document, ogData, }: { @@ -40,4 +40,4 @@ export function WebsitePreview({ ) : null} ) -} +}) diff --git a/apps/web/components/document-cards/youtube-preview.tsx b/apps/web/components/document-cards/youtube-preview.tsx index 47c9cb5e8..5f53d490b 100644 --- a/apps/web/components/document-cards/youtube-preview.tsx +++ b/apps/web/components/document-cards/youtube-preview.tsx @@ -1,5 +1,6 @@ "use client" +import { memo } from "react" import type { DocumentsWithMemoriesResponseSchema } from "@repo/validation/api" import type { z } from "zod" import { dmSansClassName } from "@/lib/fonts" @@ -9,7 +10,7 @@ import { extractYouTubeVideoId } from "../utils" type DocumentsResponse = z.infer type DocumentWithMemories = DocumentsResponse["documents"][0] -export function YoutubePreview({ +export const YoutubePreview = memo(function YoutubePreview({ document, }: { document: DocumentWithMemories @@ -49,4 +50,4 @@ export function YoutubePreview({ ) -} +}) From e7144407f5ad0c7c33a25ba9630e8f1c51d99d75 Mon Sep 17 00:00:00 2001 From: Ishaan Gupta Date: Mon, 13 Apr 2026 04:17:45 +0530 Subject: [PATCH 9/9] perf(lib): optimize subscription status lookup to O(n) (#848) --- packages/lib/queries.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/lib/queries.ts b/packages/lib/queries.ts index f3b4d745c..a64250c7f 100644 --- a/packages/lib/queries.ts +++ b/packages/lib/queries.ts @@ -37,8 +37,11 @@ export function getSubscriptionStatus( ): SubscriptionStatusMap { const statusMap: SubscriptionStatusMap = { ...DEFAULT_SUBSCRIPTION_STATUS } if (!products) return statusMap + + const productMap = new Map(products.map((p) => [p.id, p])) + for (const tier of PLAN_TIERS) { - const product = products.find((p) => p.id === tier) + const product = productMap.get(tier) statusMap[tier] = { allowed: product?.status === "active", status: product?.status ?? null,