import { deduplicateMemories } from "../shared" import type { Logger } from "./logger" import { type LanguageModelCallOptions, convertProfileToMarkdown, type ProfileStructure, } from "./util" /** * Data provided to the prompt template function for customizing memory injection. */ export interface MemoryPromptData { /** * Pre-formatted markdown combining static and dynamic profile memories. * Contains core user facts (name, preferences, goals) and recent context (projects, interests). */ userMemories: string /** * Pre-formatted search results text for the current query. * Contains memories retrieved based on semantic similarity to the conversation. * Empty string if mode is "profile" only. */ generalSearchMemories: string } /** * Function type for customizing the memory prompt injection. * Return the full string to be injected into the system prompt. * * @example * ```typescript * const promptTemplate: PromptTemplate = (data) => ` * * Here is some information about your past conversations: * ${data.userMemories} * ${data.generalSearchMemories} * * `.trim() * ``` */ export type PromptTemplate = (data: MemoryPromptData) => string /** * Default prompt template that replicates the original behavior. */ export const defaultPromptTemplate: PromptTemplate = (data) => `User Supermemories: \n${data.userMemories}\n${data.generalSearchMemories}`.trim() export const normalizeBaseUrl = (url?: string): string => { const defaultUrl = "https://api.supermemory.ai" if (!url) return defaultUrl return url.endsWith("/") ? url.slice(0, -1) : url } const supermemoryProfileSearch = async ( containerTag: string, queryText: string, baseUrl: string, apiKey: 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 ${apiKey}`, }, 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, containerTag: string, logger: Logger, mode: "profile" | "query" | "full", baseUrl: string, apiKey: string, promptTemplate: PromptTemplate = defaultPromptTemplate, ): Promise => { const systemPromptExists = params.prompt.some( (prompt) => prompt.role === "system", ) const queryText = mode !== "profile" ? params.prompt .slice() .reverse() .find((prompt: { role: string }) => prompt.role === "user") ?.content?.filter( (content: { type: string }) => content.type === "text", ) ?.map((content: { type: string; text: string }) => content.type === "text" ? content.text : "", ) ?.join(" ") || "" : "" const memoriesResponse = await supermemoryProfileSearch( containerTag, queryText, baseUrl, apiKey, ) 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 userMemories = mode !== "query" ? convertProfileToMarkdown({ profile: { static: deduplicated.static, dynamic: deduplicated.dynamic, }, searchResults: { results: [] }, }) : "" const generalSearchMemories = mode !== "profile" ? `Search results for user's recent message: \n${deduplicated.searchResults .map((memory) => `- ${memory}`) .join("\n")}` : "" const promptData: MemoryPromptData = { userMemories, generalSearchMemories, } const memories = promptTemplate(promptData) 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 const newPrompt = params.prompt.map((prompt: any) => prompt.role === "system" ? { ...prompt, content: `${prompt.content} \n ${memories}` } : prompt, ) return { ...params, prompt: newPrompt } as LanguageModelCallOptions } logger.debug( "System prompt does not exist, created system prompt with memories", ) const newPrompt = [ { role: "system" as const, content: memories }, ...params.prompt, // biome-ignore lint/suspicious/noExplicitAny: Union type compatibility between V2 and V3 prompt types ] as any return { ...params, prompt: newPrompt } as LanguageModelCallOptions }