feat(@supermemory/tools): vercel ai sdk compatbile with v5 and v6 (#628)

This commit is contained in:
MaheshtheDev 2025-12-24 01:36:03 +00:00 committed by Sreeram Sreedhar
parent 5493455f69
commit 0e1f062fa9
5 changed files with 327 additions and 366 deletions

View file

@ -1,11 +1,11 @@
{
"name": "@supermemory/tools",
"type": "module",
"version": "1.4.4",
"version": "1.3.62",
"description": "Memory tools for AI SDK and OpenAI function calling with supermemory",
"scripts": {
"build": "tsdown",
"dev": "tsdown --watch --ignore-watch .turbo",
"dev": "tsdown --watch",
"check-types": "tsc --noEmit",
"test": "vitest --testTimeout 100000",
"test:watch": "vitest --watch --testTimeout 100000"
@ -14,31 +14,22 @@
"@ai-sdk/anthropic": "^2.0.25",
"@ai-sdk/openai": "^2.0.23",
"ai": "^5.0.29",
"lru-cache": "^11.2.6",
"openai": "^4.104.0",
"supermemory": "^3.0.0-alpha.26",
"zod": "^4.1.5"
},
"devDependencies": {
"@ai-sdk/provider": "^3.0.0",
"@anthropic-ai/sdk": "^0.65.0",
"@voltagent/core": "^2.6.12",
"@mastra/core": "^1.0.0",
"@total-typescript/tsconfig": "^1.0.4",
"@types/bun": "^1.2.21",
"dotenv": "^16.6.1",
"tsdown": "^0.14.2",
"typescript": "^5.9.2",
"vitest": "^3.2.4"
"vitest": "^3.2.4",
"@anthropic-ai/sdk": "^0.65.0"
},
"peerDependencies": {
"@ai-sdk/provider": "^2.0.0 || ^3.0.0",
"@voltagent/core": "^2.6.12"
},
"peerDependenciesMeta": {
"@voltagent/core": {
"optional": true
}
"@ai-sdk/provider": "^2.0.0 || ^3.0.0"
},
"main": "./dist/index.js",
"module": "./dist/index.js",
@ -50,9 +41,7 @@
".": "./dist/index.js",
"./ai-sdk": "./dist/ai-sdk.js",
"./claude-memory": "./dist/claude-memory.js",
"./mastra": "./dist/mastra.js",
"./openai": "./dist/openai/index.js",
"./voltagent": "./dist/voltagent/index.js",
"./package.json": "./package.json"
},
"repository": {

View file

@ -10,46 +10,14 @@ import {
extractAssistantResponseText,
saveMemoryAfterResponse,
} from "./middleware"
import type { PromptTemplate, MemoryPromptData } from "./memory-prompt"
interface WrapVercelLanguageModelOptions {
/** Optional conversation ID to group messages for contextual memory generation */
conversationId?: string
/** Enable detailed logging of memory search and injection */
verbose?: boolean
/**
* Memory retrieval mode:
* - "profile": Retrieves user profile memories (static + dynamic) without query filtering
* - "query": Searches memories based on semantic similarity to the user's message
* - "full": Combines both profile and query-based results
*/
mode?: "profile" | "query" | "full"
/**
* Memory persistence mode:
* - "always": Automatically save conversations as memories
* - "never": Only retrieve memories, don't store new ones
*/
addMemory?: "always" | "never"
/** Supermemory API key (falls back to SUPERMEMORY_API_KEY env var) */
apiKey?: string
/** Custom Supermemory API base URL */
baseUrl?: string
/**
* Custom function to format memory data into the system prompt.
* If not provided, uses the default "User Supermemories:" format.
*
* @example
* ```typescript
* promptTemplate: (data) => `
* <user_memories>
* Here is some information about your past conversations:
* ${data.userMemories}
* ${data.generalSearchMemories}
* </user_memories>
* `.trim()
* ```
*/
promptTemplate?: PromptTemplate
}
/**
@ -116,121 +84,103 @@ const wrapVercelLanguageModel = <T extends LanguageModel>(
mode: options?.mode ?? "profile",
addMemory: options?.addMemory ?? "never",
baseUrl: options?.baseUrl,
promptTemplate: options?.promptTemplate,
})
// Proxy keeps prototype/getter fields (e.g. provider, modelId) that `{ ...model }` drops.
return new Proxy(model, {
get(target, prop, receiver) {
if (prop === "doGenerate") {
return async (params: LanguageModelCallOptions) => {
try {
const transformedParams = await transformParamsWithMemory(
params,
ctx,
)
const wrappedModel = {
...model,
// biome-ignore lint/suspicious/noExplicitAny: Union type compatibility between V2 and V3
const result = await target.doGenerate(transformedParams as any)
doGenerate: async (params: LanguageModelCallOptions) => {
try {
const transformedParams = await transformParamsWithMemory(params, ctx)
// biome-ignore lint/suspicious/noExplicitAny: Union type compatibility between V2 and V3
const result = await model.doGenerate(transformedParams as any)
const userMessage = getLastUserMessage(params)
if (ctx.addMemory === "always" && userMessage && userMessage.trim()) {
const assistantResponseText = extractAssistantResponseText(
result.content as unknown[],
)
saveMemoryAfterResponse(
ctx.client,
ctx.containerTag,
ctx.conversationId,
assistantResponseText,
params,
ctx.logger,
ctx.apiKey,
ctx.normalizedBaseUrl,
)
}
return result
} catch (error) {
ctx.logger.error("Error generating response", {
error: error instanceof Error ? error.message : "Unknown error",
})
throw error
}
},
doStream: async (params: LanguageModelCallOptions) => {
let generatedText = ""
try {
const transformedParams = await transformParamsWithMemory(params, ctx)
const { stream, ...rest } = await model.doStream(
// biome-ignore lint/suspicious/noExplicitAny: Union type compatibility between V2 and V3
transformedParams as any,
)
const transformStream = new TransformStream<
LanguageModelStreamPart,
LanguageModelStreamPart
>({
transform(chunk, controller) {
if (chunk.type === "text-delta") {
generatedText += chunk.delta
}
controller.enqueue(chunk)
},
flush: async () => {
const userMessage = getLastUserMessage(params)
if (
ctx.addMemory === "always" &&
userMessage &&
userMessage.trim()
) {
const assistantResponseText = extractAssistantResponseText(
result.content as unknown[],
)
saveMemoryAfterResponse(
ctx.client,
ctx.containerTag,
ctx.conversationId,
assistantResponseText,
generatedText,
params,
ctx.logger,
ctx.apiKey,
ctx.normalizedBaseUrl,
)
}
},
})
return result
} catch (error) {
ctx.logger.error("Error generating response", {
error: error instanceof Error ? error.message : "Unknown error",
})
throw error
}
return {
stream: stream.pipeThrough(transformStream),
...rest,
}
} catch (error) {
ctx.logger.error("Error streaming response", {
error: error instanceof Error ? error.message : "Unknown error",
})
throw error
}
if (prop === "doStream") {
return async (params: LanguageModelCallOptions) => {
let generatedText = ""
try {
const transformedParams = await transformParamsWithMemory(
params,
ctx,
)
const { stream, ...rest } = await target.doStream(
// biome-ignore lint/suspicious/noExplicitAny: Union type compatibility between V2 and V3
transformedParams as any,
)
const transformStream = new TransformStream<
LanguageModelStreamPart,
LanguageModelStreamPart
>({
transform(chunk, controller) {
if (chunk.type === "text-delta") {
generatedText += chunk.delta
}
controller.enqueue(chunk)
},
flush: async () => {
const userMessage = getLastUserMessage(params)
if (
ctx.addMemory === "always" &&
userMessage &&
userMessage.trim()
) {
saveMemoryAfterResponse(
ctx.client,
ctx.containerTag,
ctx.conversationId,
generatedText,
params,
ctx.logger,
ctx.apiKey,
ctx.normalizedBaseUrl,
)
}
},
})
return {
stream: stream.pipeThrough(transformStream),
...rest,
}
} catch (error) {
ctx.logger.error("Error streaming response", {
error: error instanceof Error ? error.message : "Unknown error",
})
throw error
}
}
}
return Reflect.get(target, prop, receiver)
},
}) as T
} as T
return wrappedModel
}
export {
wrapVercelLanguageModel as withSupermemory,
type WrapVercelLanguageModelOptions as WithSupermemoryOptions,
type PromptTemplate,
type MemoryPromptData,
}

View file

@ -1,70 +1,144 @@
// Re-export shared types and functions
export {
type MemoryPromptData,
type PromptTemplate,
defaultPromptTemplate,
normalizeBaseUrl,
buildMemoriesText,
type BuildMemoriesTextOptions,
} from "../shared"
import { deduplicateMemories } from "../shared"
import type { Logger } from "./logger"
import {
type LanguageModelCallOptions,
convertProfileToMarkdown,
type ProfileStructure,
} from "./util"
import type { Logger, MemoryPromptData } from "../shared"
import type { LanguageModelCallOptions } from "./util"
/**
* Extracts the query text from params based on mode.
* For "profile" mode, returns empty string (no query needed).
* For "query" or "full" mode, extracts the last user message text.
*
* @param params - The language model call options
* @param mode - The memory retrieval mode
* @returns The query text for memory search
*/
export const extractQueryText = (
params: LanguageModelCallOptions,
mode: "profile" | "query" | "full",
): string => {
if (mode === "profile") {
return ""
}
const userMessage = params.prompt
.slice()
.reverse()
.find((prompt: { role: string }) => prompt.role === "user")
const content = userMessage?.content
if (!content) return ""
if (typeof content === "string") {
return content
}
// biome-ignore lint/suspicious/noExplicitAny: Union type compatibility between V2 and V3
return (content as any[])
.filter((part) => part.type === "text")
.map((part) => part.text || "")
.join(" ")
export const normalizeBaseUrl = (url?: string): string => {
const defaultUrl = "https://api.supermemory.ai"
if (!url) return defaultUrl
return url.endsWith("/") ? url.slice(0, -1) : url
}
/**
* Injects memories string into params by appending to existing system prompt
* or creating a new one. Pure function - does not mutate the original params.
*
* @param params - The language model call options
* @param memories - The formatted memories string to inject
* @param logger - Logger for debug output
* @returns New params with memories injected into the system prompt
*/
export const injectMemoriesIntoParams = (
const supermemoryProfileSearch = async (
containerTag: string,
queryText: string,
baseUrl: string,
): Promise<ProfileStructure> => {
const payload = queryText
? JSON.stringify({
q: queryText,
containerTag: containerTag,
})
: JSON.stringify({
containerTag: containerTag,
})
try {
const response = await fetch(`${baseUrl}/v4/profile`, {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${process.env.SUPERMEMORY_API_KEY}`,
},
body: payload,
})
if (!response.ok) {
const errorText = await response.text().catch(() => "Unknown error")
throw new Error(
`Supermemory profile search failed: ${response.status} ${response.statusText}. ${errorText}`,
)
}
return await response.json()
} catch (error) {
if (error instanceof Error) {
throw error
}
throw new Error(`Supermemory API request failed: ${error}`)
}
}
export const addSystemPrompt = async (
params: LanguageModelCallOptions,
memories: string,
containerTag: string,
logger: Logger,
): LanguageModelCallOptions => {
mode: "profile" | "query" | "full",
baseUrl = "https://api.supermemory.ai",
): Promise<LanguageModelCallOptions> => {
const systemPromptExists = params.prompt.some(
(prompt) => prompt.role === "system",
)
const queryText =
mode !== "profile"
? params.prompt
.slice()
.reverse()
.find((prompt) => prompt.role === "user")
?.content?.filter((content) => content.type === "text")
?.map((content) => (content.type === "text" ? content.text : ""))
?.join(" ") || ""
: ""
const memoriesResponse = await supermemoryProfileSearch(
containerTag,
queryText,
baseUrl,
)
const memoryCountStatic = memoriesResponse.profile.static?.length || 0
const memoryCountDynamic = memoriesResponse.profile.dynamic?.length || 0
logger.info("Memory search completed", {
containerTag,
memoryCountStatic,
memoryCountDynamic,
queryText:
queryText.substring(0, 100) + (queryText.length > 100 ? "..." : ""),
mode,
})
const deduplicated = deduplicateMemories({
static: memoriesResponse.profile.static,
dynamic: memoriesResponse.profile.dynamic,
searchResults: memoriesResponse.searchResults?.results,
})
logger.debug("Memory deduplication completed", {
static: {
original: memoryCountStatic,
deduplicated: deduplicated.static.length,
},
dynamic: {
original: memoryCountDynamic,
deduplicated: deduplicated.dynamic.length,
},
searchResults: {
original: memoriesResponse.searchResults.results.length,
deduplicated: deduplicated.searchResults?.length,
},
})
const profileData =
mode !== "query"
? convertProfileToMarkdown({
profile: {
static: deduplicated.static,
dynamic: deduplicated.dynamic,
},
searchResults: { results: [] },
})
: ""
const searchResultsMemories =
mode !== "profile"
? `Search results for user's recent message: \n${deduplicated.searchResults
.map((memory) => `- ${memory}`)
.join("\n")}`
: ""
const memories =
`User Supermemories: \n${profileData}\n${searchResultsMemories}`.trim()
if (memories) {
logger.debug("Memory content preview", {
content: memories,
fullLength: memories.length,
})
}
if (systemPromptExists) {
logger.debug("Added memories to existing system prompt")
// biome-ignore lint/suspicious/noExplicitAny: Union type compatibility between V2 and V3 prompt types
@ -86,35 +160,3 @@ export const injectMemoriesIntoParams = (
] as any
return { ...params, prompt: newPrompt } as LanguageModelCallOptions
}
/**
* Adds memories to the system prompt by fetching from API and injecting.
* This is the original combined function, now implemented via helpers.
*
* @deprecated Prefer using buildMemoriesText + injectMemoriesIntoParams for caching support
*/
export const addSystemPrompt = async (
params: LanguageModelCallOptions,
containerTag: string,
logger: Logger,
mode: "profile" | "query" | "full",
baseUrl: string,
apiKey: string,
promptTemplate?: (data: MemoryPromptData) => string,
): Promise<LanguageModelCallOptions> => {
const { buildMemoriesText } = await import("../shared")
const queryText = extractQueryText(params, mode)
const memories = await buildMemoriesText({
containerTag,
queryText,
mode,
baseUrl,
apiKey,
logger,
promptTemplate,
})
return injectMemoriesIntoParams(params, memories, logger)
}

View file

@ -3,23 +3,17 @@ import {
addConversation,
type ConversationMessage,
} from "../conversations-client"
import {
createLogger,
normalizeBaseUrl,
MemoryCache,
buildMemoriesText,
type Logger,
type PromptTemplate,
type MemoryMode,
} from "../shared"
import { createLogger, type Logger } from "./logger"
import {
type LanguageModelCallOptions,
type LanguageModelStreamPart,
type OutputContentItem,
getLastUserMessage,
filterOutSupermemories,
} from "./util"
import { extractQueryText, injectMemoriesIntoParams } from "./memory-prompt"
import { addSystemPrompt, normalizeBaseUrl } from "./memory-prompt"
const getConversationContent = (params: LanguageModelCallOptions) => {
export const getConversationContent = (params: LanguageModelCallOptions) => {
return params.prompt
.filter((msg) => msg.role !== "system" && msg.role !== "tool")
.map((msg) => {
@ -38,31 +32,31 @@ const getConversationContent = (params: LanguageModelCallOptions) => {
.join("\n\n")
}
const convertToConversationMessages = (
export const convertToConversationMessages = (
params: LanguageModelCallOptions,
assistantResponseText: string,
): ConversationMessage[] => {
const messages: ConversationMessage[] = []
for (const msg of params.prompt) {
if (msg.role === "system") {
continue
}
if (typeof msg.content === "string") {
if (msg.content) {
const filteredContent = filterOutSupermemories(msg.content)
if (filteredContent) {
messages.push({
role: msg.role as "user" | "assistant" | "tool",
content: msg.content,
role: msg.role as "user" | "assistant" | "system" | "tool",
content: filteredContent,
})
}
} else {
const contentParts = msg.content
.map((c) => {
if (c.type === "text" && c.text) {
return {
type: "text" as const,
text: c.text,
if (c.type === "text") {
const filteredText = filterOutSupermemories(c.text)
if (filteredText) {
return {
type: "text" as const,
text: filteredText,
}
}
}
if (
@ -81,7 +75,7 @@ const convertToConversationMessages = (
if (contentParts.length > 0) {
messages.push({
role: msg.role as "user" | "assistant" | "tool",
role: msg.role as "user" | "assistant" | "system" | "tool",
content: contentParts,
})
}
@ -139,7 +133,7 @@ export const saveMemoryAfterResponse = async (
? `${getConversationContent(params)} \n\n Assistant: ${assistantResponseText}`
: `User: ${userMessage} \n\n Assistant: ${assistantResponseText}`
const response = await client.add({
const response = await client.memories.add({
content,
containerTags: [containerTag],
customId,
@ -159,52 +153,25 @@ export const saveMemoryAfterResponse = async (
}
}
/**
* Configuration options for the Supermemory middleware.
*/
interface SupermemoryMiddlewareOptions {
/** Container tag/identifier for memory search (e.g., user ID, project ID) */
export interface SupermemoryMiddlewareOptions {
containerTag: string
/** Supermemory API key */
apiKey: string
/** Optional conversation ID to group messages for contextual memory generation */
conversationId?: string
/** Enable detailed logging of memory search and injection */
verbose?: boolean
/**
* Memory retrieval mode:
* - "profile": Retrieves user profile memories (static + dynamic) without query filtering
* - "query": Searches memories based on semantic similarity to the user's message
* - "full": Combines both profile and query-based results
*/
mode?: MemoryMode
/**
* Memory persistence mode:
* - "always": Automatically save conversations as memories
* - "never": Only retrieve memories, don't store new ones
*/
mode?: "profile" | "query" | "full"
addMemory?: "always" | "never"
/** Custom Supermemory API base URL */
baseUrl?: string
/** Custom function to format memory data into the system prompt */
promptTemplate?: PromptTemplate
}
interface SupermemoryMiddlewareContext {
export interface SupermemoryMiddlewareContext {
client: Supermemory
logger: Logger
containerTag: string
conversationId?: string
mode: MemoryMode
mode: "profile" | "query" | "full"
addMemory: "always" | "never"
normalizedBaseUrl: string
apiKey: string
promptTemplate?: PromptTemplate
/**
* Per-turn memory cache. Stores the injected memories string for each
* user turn (keyed by turnKey) to avoid redundant API calls during tool-call
*/
memoryCache: MemoryCache<string>
}
export const createSupermemoryContext = (
@ -218,7 +185,6 @@ export const createSupermemoryContext = (
mode = "profile",
addMemory = "never",
baseUrl,
promptTemplate,
} = options
const logger = createLogger(verbose)
@ -240,35 +206,9 @@ export const createSupermemoryContext = (
addMemory,
normalizedBaseUrl,
apiKey,
promptTemplate,
memoryCache: new MemoryCache<string>(),
}
}
/**
* Generates a cache key for the current turn based on context and user message.
* Uses the shared MemoryCache.makeTurnKey implementation.
*/
const makeTurnKey = (
ctx: SupermemoryMiddlewareContext,
userMessage: string,
): string => {
return MemoryCache.makeTurnKey(
ctx.containerTag,
ctx.conversationId,
ctx.mode,
userMessage,
)
}
/**
* Checks if this is a new user turn (last message is from user)
*/
const isNewUserTurn = (params: LanguageModelCallOptions): boolean => {
const lastMessage = params.prompt.at(-1)
return lastMessage?.role === "user"
}
export const transformParamsWithMemory = async (
params: LanguageModelCallOptions,
ctx: SupermemoryMiddlewareContext,
@ -282,42 +222,20 @@ export const transformParamsWithMemory = async (
}
}
const turnKey = makeTurnKey(ctx, userMessage || "")
const isNewTurn = isNewUserTurn(params)
// Check if we can use cached memories
const cachedMemories = ctx.memoryCache.get(turnKey)
if (!isNewTurn && cachedMemories) {
ctx.logger.debug("Using cached memories: ", {
turnKey,
})
return injectMemoriesIntoParams(params, cachedMemories, ctx.logger)
}
ctx.logger.info("Starting memory search", {
containerTag: ctx.containerTag,
conversationId: ctx.conversationId,
mode: ctx.mode,
isNewTurn,
cacheHit: false,
})
const queryText = extractQueryText(params, ctx.mode)
const memories = await buildMemoriesText({
containerTag: ctx.containerTag,
queryText,
mode: ctx.mode,
baseUrl: ctx.normalizedBaseUrl,
apiKey: ctx.apiKey,
logger: ctx.logger,
promptTemplate: ctx.promptTemplate,
})
ctx.memoryCache.set(turnKey, memories)
ctx.logger.debug("Cached memories for turn", { turnKey })
return injectMemoriesIntoParams(params, memories, ctx.logger)
const transformedParams = await addSystemPrompt(
params,
ctx.containerTag,
ctx.logger,
ctx.mode,
ctx.normalizedBaseUrl,
)
return transformedParams
}
export const extractAssistantResponseText = (content: unknown[]): string => {
@ -325,3 +243,47 @@ export const extractAssistantResponseText = (content: unknown[]): string => {
.map((item) => (item.type === "text" ? item.text || "" : ""))
.join("")
}
export const createStreamTransform = (
ctx: SupermemoryMiddlewareContext,
params: LanguageModelCallOptions,
): {
transform: TransformStream<LanguageModelStreamPart, LanguageModelStreamPart>
getGeneratedText: () => string
} => {
let generatedText = ""
const transform = new TransformStream<
LanguageModelStreamPart,
LanguageModelStreamPart
>({
transform(chunk, controller) {
if (chunk.type === "text-delta") {
generatedText += chunk.delta
}
controller.enqueue(chunk)
},
flush: async () => {
const userMessage = getLastUserMessage(params)
if (ctx.addMemory === "always" && userMessage && userMessage.trim()) {
saveMemoryAfterResponse(
ctx.client,
ctx.containerTag,
ctx.conversationId,
generatedText,
params,
ctx.logger,
ctx.apiKey,
ctx.normalizedBaseUrl,
)
}
},
})
return {
transform,
getGeneratedText: () => generatedText,
}
}
export { createLogger, type Logger, type OutputContentItem }

View file

@ -9,12 +9,6 @@ import type {
LanguageModelV3StreamPart,
} from "@ai-sdk/provider"
// Re-export shared types for backward compatibility
export type {
ProfileStructure,
ProfileMarkdownData,
} from "../shared"
// Union types for dual SDK version support (V2 = SDK 5, V3 = SDK 6)
export type LanguageModel = LanguageModelV2 | LanguageModelV3
export type LanguageModelCallOptions =
@ -27,6 +21,26 @@ export type LanguageModelStreamPart =
| LanguageModelV2StreamPart
| LanguageModelV3StreamPart
export interface ProfileStructure {
profile: {
static?: Array<{ memory: string; metadata?: Record<string, unknown> }>
dynamic?: Array<{ memory: string; metadata?: Record<string, unknown> }>
}
searchResults: {
results: Array<{ memory: string; metadata?: Record<string, unknown> }>
}
}
export interface ProfileMarkdownData {
profile: {
static?: string[]
dynamic?: string[]
}
searchResults: {
results: Array<{ memory: string }>
}
}
export type OutputContentItem =
| { type: "text"; text: string }
| { type: "reasoning"; text: string }
@ -44,33 +58,37 @@ export type OutputContentItem =
title: string
}
// Re-export convertProfileToMarkdown from shared for backward compatibility
export { convertProfileToMarkdown } from "../shared"
/**
* Convert profile data to markdown format
* @param data Profile data with string arrays for static and dynamic memories
* @returns Markdown string with profile sections
*/
export function convertProfileToMarkdown(data: ProfileMarkdownData): string {
const sections: string[] = []
export const getLastUserMessage = (
params: LanguageModelCallOptions,
): string | undefined => {
if (data.profile.static && data.profile.static.length > 0) {
sections.push("## Static Profile")
sections.push(data.profile.static.map((item) => `- ${item}`).join("\n"))
}
if (data.profile.dynamic && data.profile.dynamic.length > 0) {
sections.push("## Dynamic Profile")
sections.push(data.profile.dynamic.map((item) => `- ${item}`).join("\n"))
}
return sections.join("\n\n")
}
export const getLastUserMessage = (params: LanguageModelCallOptions) => {
const lastUserMessage = params.prompt
.slice()
.reverse()
.find((prompt: LanguageModelMessage) => prompt.role === "user")
if (!lastUserMessage) {
return undefined
}
const content = lastUserMessage.content
// Handle string content directly
if (typeof content === "string") {
return content
}
// Handle array content - extract text parts
return content
.filter((part) => part.type === "text")
.map((part) => (part as { type: "text"; text: string }).text)
const memories = lastUserMessage?.content
.filter((content) => content.type === "text")
.map((content) => (content as { type: "text"; text: string }).text)
.join(" ")
return memories
}
export const filterOutSupermemories = (content: string) => {