diff --git a/packages/tools/package.json b/packages/tools/package.json index c4a39da4..d56a7dd1 100644 --- a/packages/tools/package.json +++ b/packages/tools/package.json @@ -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": { diff --git a/packages/tools/src/vercel/index.ts b/packages/tools/src/vercel/index.ts index b56b76a3..0eedc753 100644 --- a/packages/tools/src/vercel/index.ts +++ b/packages/tools/src/vercel/index.ts @@ -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) => ` - * - * Here is some information about your past conversations: - * ${data.userMemories} - * ${data.generalSearchMemories} - * - * `.trim() - * ``` - */ - promptTemplate?: PromptTemplate } /** @@ -116,121 +84,103 @@ const wrapVercelLanguageModel = ( 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, } diff --git a/packages/tools/src/vercel/memory-prompt.ts b/packages/tools/src/vercel/memory-prompt.ts index 0f928aa2..1a9a05dd 100644 --- a/packages/tools/src/vercel/memory-prompt.ts +++ b/packages/tools/src/vercel/memory-prompt.ts @@ -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 => { + 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 => { 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 => { - 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) -} diff --git a/packages/tools/src/vercel/middleware.ts b/packages/tools/src/vercel/middleware.ts index 8c31b86f..a2dd77ee 100644 --- a/packages/tools/src/vercel/middleware.ts +++ b/packages/tools/src/vercel/middleware.ts @@ -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 } 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(), } } -/** - * 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 + 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 } diff --git a/packages/tools/src/vercel/util.ts b/packages/tools/src/vercel/util.ts index 49ab30c5..572f44a3 100644 --- a/packages/tools/src/vercel/util.ts +++ b/packages/tools/src/vercel/util.ts @@ -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 }> + dynamic?: Array<{ memory: string; metadata?: Record }> + } + searchResults: { + results: Array<{ memory: string; metadata?: Record }> + } +} + +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) => {