updated ai sdk

This commit is contained in:
Sreeram Sreedhar 2026-04-13 18:41:38 -07:00
parent 1030375094
commit 7b99822ae3
6 changed files with 427 additions and 727 deletions

View file

@ -32,10 +32,14 @@ Automatically inject user profiles into every LLM call for instant personalizati
```typescript
import { generateText } from "ai"
import { withSupermemory } from "@supermemory/tools/ai-sdk"
import { withSupermemory } from "@supermemory/tools/vercel"
import { openai } from "@ai-sdk/openai"
const modelWithMemory = withSupermemory(openai("gpt-5"), "user-123")
const modelWithMemory = withSupermemory({
model: openai("gpt-4"),
containerTag: "user-123",
customId: "conv-456"
})
const result = await generateText({
model: modelWithMemory,
@ -44,11 +48,14 @@ const result = await generateText({
```
<Note>
**Memory saving is disabled by default.** The middleware only retrieves existing memories. To automatically save new memories:
**Memory saving is enabled by default.** The middleware automatically saves conversations to memory. To disable memory saving:
```typescript
const modelWithMemory = withSupermemory(openai("gpt-5"), "user-123", {
addMemory: "always"
const modelWithMemory = withSupermemory({
model: openai("gpt-4"),
containerTag: "user-123",
customId: "conv-456",
addMemory: "never"
})
```
</Note>
@ -58,19 +65,34 @@ const result = await generateText({
**Profile Mode (Default)** - Retrieves the user's complete profile:
```typescript
const model = withSupermemory(openai("gpt-4"), "user-123", { mode: "profile" })
const model = withSupermemory({
model: openai("gpt-4"),
containerTag: "user-123",
customId: "conv-456",
mode: "profile"
})
```
**Query Mode** - Searches memories based on the user's message:
```typescript
const model = withSupermemory(openai("gpt-4"), "user-123", { mode: "query" })
const model = withSupermemory({
model: openai("gpt-4"),
containerTag: "user-123",
customId: "conv-456",
mode: "query"
})
```
**Full Mode** - Combines profile AND query-based search:
```typescript
const model = withSupermemory(openai("gpt-4"), "user-123", { mode: "full" })
const model = withSupermemory({
model: openai("gpt-4"),
containerTag: "user-123",
customId: "conv-456",
mode: "full"
})
```
### Custom Prompt Templates
@ -78,7 +100,7 @@ const model = withSupermemory(openai("gpt-4"), "user-123", { mode: "full" })
Customize how memories are formatted. The template receives `userMemories`, `generalSearchMemories`, and `searchResults` (raw array for filtering by metadata):
```typescript
import { withSupermemory, type MemoryPromptData } from "@supermemory/tools/ai-sdk"
import { withSupermemory, type MemoryPromptData } from "@supermemory/tools/vercel"
const claudePrompt = (data: MemoryPromptData) => `
<context>
@ -91,7 +113,10 @@ const claudePrompt = (data: MemoryPromptData) => `
</context>
`.trim()
const model = withSupermemory(anthropic("claude-3-sonnet"), "user-123", {
const model = withSupermemory({
model: anthropic("claude-3-sonnet"),
containerTag: "user-123",
customId: "conv-456",
mode: "full",
promptTemplate: claudePrompt
})
@ -100,7 +125,10 @@ const model = withSupermemory(anthropic("claude-3-sonnet"), "user-123", {
### Verbose Logging
```typescript
const model = withSupermemory(openai("gpt-4"), "user-123", {
const model = withSupermemory({
model: openai("gpt-4"),
containerTag: "user-123",
customId: "conv-456",
verbose: true
})
// Console output shows memory retrieval details

496
bun.lock

File diff suppressed because it is too large Load diff

View file

@ -10,14 +10,59 @@ import {
extractAssistantResponseText,
saveMemoryAfterResponse,
} from "./middleware"
import type { PromptTemplate, MemoryPromptData } from "./memory-prompt"
interface WrapVercelLanguageModelOptions {
conversationId?: string
interface WrapVercelLanguageModelOptions<T extends LanguageModel> {
/** The language model to wrap with supermemory capabilities */
model: T
/** The container tag/identifier for memory search (e.g., user ID, project ID) */
containerTag: string
/** Custom ID to group messages into a single document. Required. */
customId: 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"
/**
* Search mode for memory retrieval:
* - "memories": Search only memory entries (default)
* - "hybrid": Search both memories AND document chunks (recommended for RAG)
* - "documents": Search only document chunks
*/
searchMode?: "memories" | "hybrid" | "documents"
/** Maximum number of search results to return when using hybrid/documents mode (default: 10) */
searchLimit?: number
/**
* Memory persistence mode:
* - "always": Automatically save conversations as memories (default)
* - "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
}
/**
@ -31,13 +76,15 @@ interface WrapVercelLanguageModelOptions {
* Supports both Vercel AI SDK 5 (LanguageModelV2) and SDK 6 (LanguageModelV3) via runtime
* detection of `model.specificationVersion`.
*
* @param model - The language model to wrap with supermemory capabilities (V2 or V3)
* @param containerTag - The container tag/identifier for memory search (e.g., user ID, project ID)
* @param options - Optional configuration options for the middleware
* @param options.conversationId - Optional conversation ID to group messages into a single document for contextual memory generation
* @param options - Configuration object containing model and Supermemory options
* @param options.model - The language model to wrap with supermemory capabilities (V2 or V3)
* @param options.containerTag - Required. The container tag/identifier for memory search (e.g., user ID, project ID)
* @param options.customId - Required. Custom ID to group messages into a single document
* @param options.verbose - Optional flag to enable detailed logging of memory search and injection process (default: false)
* @param options.mode - Optional mode for memory search: "profile", "query", or "full" (default: "profile")
* @param options.addMemory - Optional mode for memory search: "always", "never" (default: "never")
* @param options.searchMode - Optional search mode: "memories" (default), "hybrid" (memories + chunks), or "documents" (chunks only)
* @param options.searchLimit - Optional maximum number of search results when using hybrid/documents mode (default: 10)
* @param options.addMemory - Optional mode for memory persistence: "always" (default - saves conversations), "never" (read-only mode)
* @param options.apiKey - Optional Supermemory API key to use instead of the environment variable
* @param options.baseUrl - Optional base URL for the Supermemory API (default: "https://api.supermemory.ai")
*
@ -45,18 +92,32 @@ interface WrapVercelLanguageModelOptions {
*
* @example
* ```typescript
* import { withSupermemory } from "@supermemory/tools/ai-sdk"
* import { withSupermemory } from "@supermemory/tools/vercel"
* import { openai } from "@ai-sdk/openai"
* import { generateText } from "ai"
*
* const modelWithMemory = withSupermemory(openai("gpt-4"), "user-123", {
* conversationId: "conversation-456",
* // Basic usage with profile memories
* const modelWithMemory = withSupermemory({
* model: openai("gpt-4"),
* containerTag: "user-123",
* customId: "conv-456",
* mode: "full",
* addMemory: "always"
* })
*
* // RAG usage with hybrid search (memories + document chunks)
* const ragModel = withSupermemory({
* model: openai("gpt-4"),
* containerTag: "user-123",
* customId: "conv-789",
* mode: "full",
* searchMode: "hybrid", // Search both memories and document chunks
* searchLimit: 15,
* })
*
* const result = await generateText({
* model: modelWithMemory,
* messages: [{ role: "user", content: "What's my favorite programming language?" }]
* model: ragModel,
* messages: [{ role: "user", content: "What's in my documents about quarterly goals?" }]
* })
* ```
*
@ -64,11 +125,10 @@ interface WrapVercelLanguageModelOptions {
* @throws {Error} When supermemory API request fails
*/
const wrapVercelLanguageModel = <T extends LanguageModel>(
model: T,
containerTag: string,
options?: WrapVercelLanguageModelOptions,
options: WrapVercelLanguageModelOptions<T>,
): T => {
const providedApiKey = options?.apiKey ?? process.env.SUPERMEMORY_API_KEY
const { model, containerTag, customId, ...restOptions } = options
const providedApiKey = restOptions.apiKey ?? process.env.SUPERMEMORY_API_KEY
if (!providedApiKey) {
throw new Error(
@ -79,11 +139,14 @@ const wrapVercelLanguageModel = <T extends LanguageModel>(
const ctx = createSupermemoryContext({
containerTag,
apiKey: providedApiKey,
conversationId: options?.conversationId,
verbose: options?.verbose ?? false,
mode: options?.mode ?? "profile",
addMemory: options?.addMemory ?? "never",
baseUrl: options?.baseUrl,
customId,
verbose: restOptions.verbose ?? false,
mode: restOptions.mode ?? "profile",
searchMode: restOptions.searchMode ?? "memories",
searchLimit: restOptions.searchLimit ?? 10,
addMemory: restOptions.addMemory ?? "always",
baseUrl: restOptions.baseUrl,
promptTemplate: restOptions.promptTemplate,
})
const wrappedModel = {
@ -97,14 +160,19 @@ const wrapVercelLanguageModel = <T extends LanguageModel>(
const result = await model.doGenerate(transformedParams as any)
const userMessage = getLastUserMessage(params)
if (ctx.addMemory === "always" && userMessage && userMessage.trim()) {
if (
ctx.addMemory === "always" &&
ctx.customId &&
userMessage &&
userMessage.trim()
) {
const assistantResponseText = extractAssistantResponseText(
result.content as unknown[],
)
saveMemoryAfterResponse(
ctx.client,
ctx.containerTag,
ctx.conversationId,
ctx.customId,
assistantResponseText,
params,
ctx.logger,
@ -147,13 +215,14 @@ const wrapVercelLanguageModel = <T extends LanguageModel>(
const userMessage = getLastUserMessage(params)
if (
ctx.addMemory === "always" &&
ctx.customId &&
userMessage &&
userMessage.trim()
) {
saveMemoryAfterResponse(
ctx.client,
ctx.containerTag,
ctx.conversationId,
ctx.customId,
generatedText,
params,
ctx.logger,
@ -183,4 +252,6 @@ const wrapVercelLanguageModel = <T extends LanguageModel>(
export {
wrapVercelLanguageModel as withSupermemory,
type WrapVercelLanguageModelOptions as WithSupermemoryOptions,
type PromptTemplate,
type MemoryPromptData,
}

View file

@ -3,17 +3,24 @@ import {
addConversation,
type ConversationMessage,
} from "../conversations-client"
import { createLogger, type Logger } from "./logger"
import {
createLogger,
normalizeBaseUrl,
MemoryCache,
buildMemoriesText,
type Logger,
type PromptTemplate,
type MemoryMode,
type SearchMode,
} from "../shared"
import {
type LanguageModelCallOptions,
type LanguageModelStreamPart,
type OutputContentItem,
getLastUserMessage,
filterOutSupermemories,
} from "./util"
import { addSystemPrompt, normalizeBaseUrl } from "./memory-prompt"
import { extractQueryText, injectMemoriesIntoParams } from "./memory-prompt"
export const getConversationContent = (params: LanguageModelCallOptions) => {
const _getConversationContent = (params: LanguageModelCallOptions) => {
return params.prompt
.filter((msg) => msg.role !== "system" && msg.role !== "tool")
.map((msg) => {
@ -32,31 +39,31 @@ export const getConversationContent = (params: LanguageModelCallOptions) => {
.join("\n\n")
}
export const convertToConversationMessages = (
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") {
const filteredContent = filterOutSupermemories(msg.content)
if (filteredContent) {
if (msg.content) {
messages.push({
role: msg.role as "user" | "assistant" | "system" | "tool",
content: filteredContent,
role: msg.role as "user" | "assistant" | "tool",
content: msg.content,
})
}
} else {
const contentParts = msg.content
.map((c) => {
if (c.type === "text") {
const filteredText = filterOutSupermemories(c.text)
if (filteredText) {
return {
type: "text" as const,
text: filteredText,
}
if (c.type === "text" && c.text) {
return {
type: "text" as const,
text: c.text,
}
}
if (
@ -75,7 +82,7 @@ export const convertToConversationMessages = (
if (contentParts.length > 0) {
messages.push({
role: msg.role as "user" | "assistant" | "system" | "tool",
role: msg.role as "user" | "assistant" | "tool",
content: contentParts,
})
}
@ -93,58 +100,34 @@ export const convertToConversationMessages = (
}
export const saveMemoryAfterResponse = async (
client: Supermemory,
_client: Supermemory,
containerTag: string,
conversationId: string | undefined,
customId: string,
assistantResponseText: string,
params: LanguageModelCallOptions,
logger: Logger,
apiKey: string,
baseUrl: string,
): Promise<void> => {
const customId = conversationId ? `conversation:${conversationId}` : undefined
try {
if (customId && conversationId) {
const conversationMessages = convertToConversationMessages(
params,
assistantResponseText,
)
const conversationMessages = convertToConversationMessages(
params,
assistantResponseText,
)
const response = await addConversation({
conversationId,
messages: conversationMessages,
containerTags: [containerTag],
apiKey,
baseUrl,
})
logger.info("Conversation saved successfully via /v4/conversations", {
containerTag,
conversationId,
messageCount: conversationMessages.length,
responseId: response.id,
})
return
}
const userMessage = getLastUserMessage(params)
const content = conversationId
? `${getConversationContent(params)} \n\n Assistant: ${assistantResponseText}`
: `User: ${userMessage} \n\n Assistant: ${assistantResponseText}`
const response = await client.memories.add({
content,
const response = await addConversation({
conversationId: customId,
messages: conversationMessages,
containerTags: [containerTag],
customId,
apiKey,
baseUrl,
})
logger.info("Memory saved successfully via /v3/documents", {
logger.info("Conversation saved successfully via /v4/conversations", {
containerTag,
customId,
content,
contentLength: content.length,
memoryId: response.id,
messageCount: conversationMessages.length,
responseId: response.id,
})
} catch (error) {
logger.error("Error saving memory", {
@ -153,25 +136,63 @@ export const saveMemoryAfterResponse = async (
}
}
export interface SupermemoryMiddlewareOptions {
/**
* Configuration options for the Supermemory middleware.
*/
interface SupermemoryMiddlewareOptions {
/** Container tag/identifier for memory search (e.g., user ID, project ID) */
containerTag: string
/** Supermemory API key */
apiKey: string
conversationId?: string
/** Custom ID to group messages into a single document. Required. */
customId: string
/** Enable detailed logging of memory search and injection */
verbose?: boolean
mode?: "profile" | "query" | "full"
/**
* 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
/**
* Search mode for memory retrieval:
* - "memories": Search only memory entries (default)
* - "hybrid": Search both memories AND document chunks (recommended for RAG)
* - "documents": Search only document chunks
*/
searchMode?: SearchMode
/** Maximum number of search results to return (default: 10) */
searchLimit?: number
/**
* Memory persistence mode:
* - "always": Automatically save conversations as memories
* - "never": Only retrieve memories, don't store new ones
*/
addMemory?: "always" | "never"
/** Custom Supermemory API base URL */
baseUrl?: string
/** Custom function to format memory data into the system prompt */
promptTemplate?: PromptTemplate
}
export interface SupermemoryMiddlewareContext {
interface SupermemoryMiddlewareContext {
client: Supermemory
logger: Logger
containerTag: string
conversationId?: string
mode: "profile" | "query" | "full"
customId: string
mode: MemoryMode
searchMode: SearchMode
searchLimit: number
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 = (
@ -180,11 +201,14 @@ export const createSupermemoryContext = (
const {
containerTag,
apiKey,
conversationId,
customId,
verbose = false,
mode = "profile",
searchMode = "memories",
searchLimit = 10,
addMemory = "never",
baseUrl,
promptTemplate,
} = options
const logger = createLogger(verbose)
@ -201,14 +225,42 @@ export const createSupermemoryContext = (
client,
logger,
containerTag,
conversationId,
customId,
mode,
searchMode,
searchLimit,
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.customId,
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,
@ -222,20 +274,45 @@ 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,
customId: ctx.customId,
mode: ctx.mode,
searchMode: ctx.searchMode,
isNewTurn,
cacheHit: false,
})
const transformedParams = await addSystemPrompt(
params,
ctx.containerTag,
ctx.logger,
ctx.mode,
ctx.normalizedBaseUrl,
)
return transformedParams
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,
searchMode: ctx.searchMode,
searchLimit: ctx.searchLimit,
})
ctx.memoryCache.set(turnKey, memories)
ctx.logger.debug("Cached memories for turn", { turnKey })
return injectMemoriesIntoParams(params, memories, ctx.logger)
}
export const extractAssistantResponseText = (content: unknown[]): string => {
@ -243,47 +320,3 @@ 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

@ -96,14 +96,13 @@ describe.skipIf(!shouldRunIntegration)(
const { model, getCapturedGenerateParams } =
createIntegrationMockModel()
const wrapped = withSupermemory(
const wrapped = withSupermemory({
model,
INTEGRATION_CONFIG.containerTag,
{
apiKey: INTEGRATION_CONFIG.apiKey,
mode: "profile",
},
)
containerTag: INTEGRATION_CONFIG.containerTag,
customId: `test-${Date.now()}`,
apiKey: INTEGRATION_CONFIG.apiKey,
mode: "profile",
})
await wrapped.doGenerate({
prompt: [
@ -125,18 +124,16 @@ describe.skipIf(!shouldRunIntegration)(
const { model } = createIntegrationMockModel()
const fetchSpy = vi.spyOn(globalThis, "fetch")
const conversationId = `test-generate-${Date.now()}`
const customId = `test-generate-${Date.now()}`
const wrapped = withSupermemory(
const wrapped = withSupermemory({
model,
INTEGRATION_CONFIG.containerTag,
{
apiKey: INTEGRATION_CONFIG.apiKey,
mode: "profile",
addMemory: "always",
conversationId,
},
)
containerTag: INTEGRATION_CONFIG.containerTag,
customId,
apiKey: INTEGRATION_CONFIG.apiKey,
mode: "profile",
addMemory: "always",
})
await wrapped.doGenerate({
prompt: [
@ -166,21 +163,19 @@ describe.skipIf(!shouldRunIntegration)(
fetchSpy.mockRestore()
})
it("should work with conversationId for grouped memories", async () => {
it("should work with customId for grouped memories", async () => {
const { model, getCapturedGenerateParams } =
createIntegrationMockModel()
const conversationId = `test-conversation-${Date.now()}`
const customId = `test-conversation-${Date.now()}`
const wrapped = withSupermemory(
const wrapped = withSupermemory({
model,
INTEGRATION_CONFIG.containerTag,
{
apiKey: INTEGRATION_CONFIG.apiKey,
mode: "profile",
conversationId,
},
)
containerTag: INTEGRATION_CONFIG.containerTag,
customId,
apiKey: INTEGRATION_CONFIG.apiKey,
mode: "profile",
})
await wrapped.doGenerate({
prompt: [
@ -203,14 +198,13 @@ describe.skipIf(!shouldRunIntegration)(
it("should fetch memories and stream response", async () => {
const { model, getCapturedStreamParams } = createIntegrationMockModel()
const wrapped = withSupermemory(
const wrapped = withSupermemory({
model,
INTEGRATION_CONFIG.containerTag,
{
apiKey: INTEGRATION_CONFIG.apiKey,
mode: "profile",
},
)
containerTag: INTEGRATION_CONFIG.containerTag,
customId: `test-stream-${Date.now()}`,
apiKey: INTEGRATION_CONFIG.apiKey,
mode: "profile",
})
const { stream } = await wrapped.doStream({
prompt: [
@ -240,18 +234,16 @@ describe.skipIf(!shouldRunIntegration)(
const { model } = createIntegrationMockModel()
const fetchSpy = vi.spyOn(globalThis, "fetch")
const conversationId = `test-stream-${Date.now()}`
const customId = `test-stream-${Date.now()}`
const wrapped = withSupermemory(
const wrapped = withSupermemory({
model,
INTEGRATION_CONFIG.containerTag,
{
apiKey: INTEGRATION_CONFIG.apiKey,
mode: "profile",
addMemory: "always",
conversationId,
},
)
containerTag: INTEGRATION_CONFIG.containerTag,
customId,
apiKey: INTEGRATION_CONFIG.apiKey,
mode: "profile",
addMemory: "always",
})
const { stream } = await wrapped.doStream({
prompt: [
@ -286,14 +278,13 @@ describe.skipIf(!shouldRunIntegration)(
it("should handle text-delta chunks correctly", async () => {
const { model } = createIntegrationMockModel()
const wrapped = withSupermemory(
const wrapped = withSupermemory({
model,
INTEGRATION_CONFIG.containerTag,
{
apiKey: INTEGRATION_CONFIG.apiKey,
mode: "profile",
},
)
containerTag: INTEGRATION_CONFIG.containerTag,
customId: `test-chunks-${Date.now()}`,
apiKey: INTEGRATION_CONFIG.apiKey,
mode: "profile",
})
const { stream } = await wrapped.doStream({
prompt: [
@ -327,14 +318,13 @@ describe.skipIf(!shouldRunIntegration)(
const { model } = createIntegrationMockModel()
const fetchSpy = vi.spyOn(globalThis, "fetch")
const wrapped = withSupermemory(
const wrapped = withSupermemory({
model,
INTEGRATION_CONFIG.containerTag,
{
apiKey: INTEGRATION_CONFIG.apiKey,
mode: "profile",
},
)
containerTag: INTEGRATION_CONFIG.containerTag,
customId: `test-profile-${Date.now()}`,
apiKey: INTEGRATION_CONFIG.apiKey,
mode: "profile",
})
await wrapped.doGenerate({
prompt: [
@ -368,14 +358,13 @@ describe.skipIf(!shouldRunIntegration)(
const { model } = createIntegrationMockModel()
const fetchSpy = vi.spyOn(globalThis, "fetch")
const wrapped = withSupermemory(
const wrapped = withSupermemory({
model,
INTEGRATION_CONFIG.containerTag,
{
apiKey: INTEGRATION_CONFIG.apiKey,
mode: "query",
},
)
containerTag: INTEGRATION_CONFIG.containerTag,
customId: `test-query-${Date.now()}`,
apiKey: INTEGRATION_CONFIG.apiKey,
mode: "query",
})
await wrapped.doGenerate({
prompt: [
@ -409,14 +398,13 @@ describe.skipIf(!shouldRunIntegration)(
const { model } = createIntegrationMockModel()
const fetchSpy = vi.spyOn(globalThis, "fetch")
const wrapped = withSupermemory(
const wrapped = withSupermemory({
model,
INTEGRATION_CONFIG.containerTag,
{
apiKey: INTEGRATION_CONFIG.apiKey,
mode: "full",
},
)
containerTag: INTEGRATION_CONFIG.containerTag,
customId: `test-full-${Date.now()}`,
apiKey: INTEGRATION_CONFIG.apiKey,
mode: "full",
})
await wrapped.doGenerate({
prompt: [
@ -456,15 +444,14 @@ describe.skipIf(!shouldRunIntegration)(
generalSearchMemories: string
}) => `<custom-memories>${data.userMemories}</custom-memories>`
const wrapped = withSupermemory(
const wrapped = withSupermemory({
model,
INTEGRATION_CONFIG.containerTag,
{
apiKey: INTEGRATION_CONFIG.apiKey,
mode: "profile",
promptTemplate: customTemplate,
},
)
containerTag: INTEGRATION_CONFIG.containerTag,
customId: `test-template-${Date.now()}`,
apiKey: INTEGRATION_CONFIG.apiKey,
mode: "profile",
promptTemplate: customTemplate,
})
await wrapped.doGenerate({
prompt: [
@ -485,15 +472,14 @@ describe.skipIf(!shouldRunIntegration)(
const { model, getCapturedGenerateParams } =
createIntegrationMockModel()
const wrapped = withSupermemory(
const wrapped = withSupermemory({
model,
INTEGRATION_CONFIG.containerTag,
{
apiKey: INTEGRATION_CONFIG.apiKey,
mode: "profile",
verbose: true,
},
)
containerTag: INTEGRATION_CONFIG.containerTag,
customId: `test-verbose-${Date.now()}`,
apiKey: INTEGRATION_CONFIG.apiKey,
mode: "profile",
verbose: true,
})
await wrapped.doGenerate({
prompt: [
@ -514,15 +500,14 @@ describe.skipIf(!shouldRunIntegration)(
const fetchSpy = vi.spyOn(globalThis, "fetch")
// Use the configured base URL (or default)
const wrapped = withSupermemory(
const wrapped = withSupermemory({
model,
INTEGRATION_CONFIG.containerTag,
{
apiKey: INTEGRATION_CONFIG.apiKey,
mode: "profile",
baseUrl: INTEGRATION_CONFIG.baseUrl,
},
)
containerTag: INTEGRATION_CONFIG.containerTag,
customId: `test-baseurl-${Date.now()}`,
apiKey: INTEGRATION_CONFIG.apiKey,
mode: "profile",
baseUrl: INTEGRATION_CONFIG.baseUrl,
})
await wrapped.doGenerate({
prompt: [
@ -556,14 +541,13 @@ describe.skipIf(!shouldRunIntegration)(
new Error("Model error"),
)
const wrapped = withSupermemory(
const wrapped = withSupermemory({
model,
INTEGRATION_CONFIG.containerTag,
{
apiKey: INTEGRATION_CONFIG.apiKey,
mode: "profile",
},
)
containerTag: INTEGRATION_CONFIG.containerTag,
customId: `test-error-${Date.now()}`,
apiKey: INTEGRATION_CONFIG.apiKey,
mode: "profile",
})
await expect(
wrapped.doGenerate({
@ -580,14 +564,13 @@ describe.skipIf(!shouldRunIntegration)(
it("should handle invalid API key gracefully", async () => {
const { model } = createIntegrationMockModel()
const wrapped = withSupermemory(
const wrapped = withSupermemory({
model,
INTEGRATION_CONFIG.containerTag,
{
apiKey: "invalid-api-key-12345",
mode: "profile",
},
)
containerTag: INTEGRATION_CONFIG.containerTag,
customId: `test-invalid-key-${Date.now()}`,
apiKey: "invalid-api-key-12345",
mode: "profile",
})
await expect(
wrapped.doGenerate({

View file

@ -73,7 +73,11 @@ describe("Unit: withSupermemory", () => {
const mockModel = createMockLanguageModel()
expect(() => {
withSupermemory(mockModel, TEST_CONFIG.containerTag)
withSupermemory({
model: mockModel,
containerTag: TEST_CONFIG.containerTag,
customId: "test-conv-123",
})
}).toThrow("SUPERMEMORY_API_KEY is not set")
})
@ -81,7 +85,11 @@ describe("Unit: withSupermemory", () => {
process.env.SUPERMEMORY_API_KEY = "test-key"
const mockModel = createMockLanguageModel()
const wrappedModel = withSupermemory(mockModel, TEST_CONFIG.containerTag)
const wrappedModel = withSupermemory({
model: mockModel,
containerTag: TEST_CONFIG.containerTag,
customId: "test-conv-456",
})
expect(wrappedModel).toBeDefined()
expect(wrappedModel.specificationVersion).toBe("v2")
@ -125,6 +133,7 @@ describe("Unit: withSupermemory", () => {
const ctx = createSupermemoryContext({
containerTag: TEST_CONFIG.containerTag,
apiKey: TEST_CONFIG.apiKey,
customId: "test-cache-123",
mode: "profile",
})
@ -157,6 +166,7 @@ describe("Unit: withSupermemory", () => {
const ctx = createSupermemoryContext({
containerTag: TEST_CONFIG.containerTag,
apiKey: TEST_CONFIG.apiKey,
customId: "test-continuation-456",
mode: "profile",
})
@ -229,6 +239,7 @@ describe("Unit: withSupermemory", () => {
const ctx = createSupermemoryContext({
containerTag: TEST_CONFIG.containerTag,
apiKey: TEST_CONFIG.apiKey,
customId: "test-refetch-789",
mode: "profile",
})
@ -289,6 +300,7 @@ describe("Unit: withSupermemory", () => {
const ctx = createSupermemoryContext({
containerTag: TEST_CONFIG.containerTag,
apiKey: TEST_CONFIG.apiKey,
customId: "test-error-101",
mode: "profile",
})
@ -310,6 +322,7 @@ describe("Unit: withSupermemory", () => {
const ctx = createSupermemoryContext({
containerTag: TEST_CONFIG.containerTag,
apiKey: TEST_CONFIG.apiKey,
customId: "test-empty-102",
mode: "query",
})
@ -327,6 +340,7 @@ describe("Unit: withSupermemory", () => {
const ctx = createSupermemoryContext({
containerTag: TEST_CONFIG.containerTag,
apiKey: TEST_CONFIG.apiKey,
customId: "test-empty-content-103",
mode: "query",
})
@ -354,6 +368,7 @@ describe("Unit: withSupermemory", () => {
const ctx = createSupermemoryContext({
containerTag: TEST_CONFIG.containerTag,
apiKey: TEST_CONFIG.apiKey,
customId: "test-mutate-104",
mode: "profile",
})