custom id implementation (#877)

This commit is contained in:
sreedharsreeram 2026-04-24 23:43:16 +00:00
parent 3c02aefe44
commit c76d27f284
17 changed files with 266 additions and 188 deletions

View file

@ -232,7 +232,7 @@ Drop-in wrappers for every major AI framework:
```typescript
// Vercel AI SDK
import { withSupermemory } from "@supermemory/tools/ai-sdk";
const model = withSupermemory(openai("gpt-4o"), "user_123");
const model = withSupermemory(openai("gpt-4o"), { containerTag: "user_123", customId: "conv-1" });
// Mastra
import { withSupermemory } from "@supermemory/tools/mastra";

View file

@ -26,7 +26,10 @@ import { withSupermemory } from "@supermemory/tools/ai-sdk"
import { openai } from "@ai-sdk/openai"
// Wrap your model with Supermemory - profiles are automatically injected
const modelWithMemory = withSupermemory(openai("gpt-5"), "user-123")
const modelWithMemory = withSupermemory(openai("gpt-5"), {
containerTag: "user-123",
customId: "conversation-456",
})
const result = await generateText({
model: modelWithMemory,
@ -39,8 +42,10 @@ const result = await generateText({
**Memory saving is disabled by default.** The middleware only retrieves existing memories. To automatically save new memories from conversations, enable it explicitly:
```typescript
const modelWithMemory = withSupermemory(openai("gpt-5"), "user-123", {
addMemory: "always"
const modelWithMemory = withSupermemory(openai("gpt-5"), {
containerTag: "user-123",
customId: "conversation-456",
addMemory: "always",
})
```
</Note>

View file

@ -20,10 +20,10 @@ import { withSupermemory } from "@supermemory/tools/ai-sdk"
import { openai } from "@ai-sdk/openai"
// Wrap any model with Supermemory middleware
const modelWithMemory = withSupermemory(
openai("gpt-4"), // Your base model
"user-123" // Container tag (user ID)
)
const modelWithMemory = withSupermemory(openai("gpt-4"), {
containerTag: "user-123",
customId: "conversation-456",
})
// Use normally - profiles are automatically injected!
const result = await generateText({
@ -49,8 +49,10 @@ All of this happens transparently - you write code as if using a normal model, b
**Memory saving is disabled by default.** The middleware only retrieves existing memories. To automatically save new memories from conversations, set `addMemory: "always"`:
```typescript
const model = withSupermemory(openai("gpt-5"), "user-123", {
addMemory: "always"
const model = withSupermemory(openai("gpt-5"), {
containerTag: "user-123",
customId: "conversation-456",
addMemory: "always",
})
```
</Note>
@ -65,11 +67,16 @@ Retrieves the user's complete profile without query-specific search. Best for ge
```typescript
// Default behavior - profile mode
const model = withSupermemory(openai("gpt-4"), "user-123")
const model = withSupermemory(openai("gpt-4"), {
containerTag: "user-123",
customId: "conv-1",
})
// Or explicitly specify
const model = withSupermemory(openai("gpt-4"), "user-123", {
mode: "profile"
const model = withSupermemory(openai("gpt-4"), {
containerTag: "user-123",
customId: "conv-1",
mode: "profile",
})
const result = await generateText({
@ -84,8 +91,10 @@ const result = await generateText({
Searches memories based on the user's specific message. Best for finding relevant information.
```typescript
const model = withSupermemory(openai("gpt-4"), "user-123", {
mode: "query"
const model = withSupermemory(openai("gpt-4"), {
containerTag: "user-123",
customId: "conv-1",
mode: "query",
})
const result = await generateText({
@ -103,8 +112,10 @@ const result = await generateText({
Combines profile AND query-based search for comprehensive context. Best for complex interactions.
```typescript
const model = withSupermemory(openai("gpt-4"), "user-123", {
mode: "full"
const model = withSupermemory(openai("gpt-4"), {
containerTag: "user-123",
customId: "conv-1",
mode: "full",
})
const result = await generateText({
@ -137,9 +148,11 @@ ${data.generalSearchMemories}
</user_memories>
`.trim()
const model = withSupermemory(openai("gpt-4"), "user-123", {
const model = withSupermemory(openai("gpt-4"), {
containerTag: "user-123",
customId: "conv-1",
mode: "full",
promptTemplate: customPrompt
promptTemplate: customPrompt,
})
const result = await generateText({
@ -174,9 +187,11 @@ const claudePrompt = (data: MemoryPromptData) => `
Use the above context to provide personalized responses.
`.trim()
const model = withSupermemory(anthropic("claude-3-sonnet"), "user-123", {
const model = withSupermemory(anthropic("claude-3-sonnet"), {
containerTag: "user-123",
customId: "conv-1",
mode: "full",
promptTemplate: claudePrompt
promptTemplate: claudePrompt,
})
```
@ -199,9 +214,11 @@ ${relevant.map((r) => `- ${r.memory}`).join("\n")}
`.trim()
}
const model = withSupermemory(openai("gpt-4"), "user-123", {
const model = withSupermemory(openai("gpt-4"), {
containerTag: "user-123",
customId: "conv-1",
mode: "full",
promptTemplate: selectivePrompt
promptTemplate: selectivePrompt,
})
```
@ -222,8 +239,10 @@ ${data.generalSearchMemories}
Use this information to provide personalized and contextually relevant responses.
`.trim()
const model = withSupermemory(openai("gpt-4"), "user-123", {
promptTemplate: brandedPrompt
const model = withSupermemory(openai("gpt-4"), {
containerTag: "user-123",
customId: "conv-1",
promptTemplate: brandedPrompt,
})
```
@ -241,8 +260,10 @@ const defaultPrompt = (data: MemoryPromptData) =>
Enable detailed logging to see exactly what's happening:
```typescript
const model = withSupermemory(openai("gpt-4"), "user-123", {
verbose: true // Enable detailed logging
const model = withSupermemory(openai("gpt-4"), {
containerTag: "user-123",
customId: "conv-1",
verbose: true, // Enable detailed logging
})
const result = await generateText({
@ -266,8 +287,11 @@ The AI SDK middleware abstracts away the complexity of manual profile management
<Tabs>
<Tab title="With AI SDK (Simple)">
```typescript
// One line setup
const model = withSupermemory(openai("gpt-4"), "user-123")
// Simple setup
const model = withSupermemory(openai("gpt-4"), {
containerTag: "user-123",
customId: "conv-1",
})
// Use normally
const result = await generateText({

View file

@ -106,7 +106,10 @@ const result = await streamText({
// Option 2: Profile middleware (automatic context injection)
import { withSupermemory } from '@supermemory/tools/ai-sdk'
const modelWithMemory = withSupermemory(anthropic('claude-3-5-sonnet-20241022'), userId)
const modelWithMemory = withSupermemory(anthropic('claude-3-5-sonnet-20241022'), {
containerTag: userId,
customId: 'conversation-1',
})
const result = await generateText({
model: modelWithMemory,

View file

@ -35,7 +35,10 @@ import { generateText } from "ai"
import { withSupermemory } from "@supermemory/tools/ai-sdk"
import { openai } from "@ai-sdk/openai"
const modelWithMemory = withSupermemory(openai("gpt-5"), "user-123")
const modelWithMemory = withSupermemory(openai("gpt-5"), {
containerTag: "user-123",
customId: "conversation-456",
})
const result = await generateText({
model: modelWithMemory,
@ -47,8 +50,10 @@ const result = await generateText({
**Memory saving is disabled by default.** The middleware only retrieves existing memories. To automatically save new memories:
```typescript
const modelWithMemory = withSupermemory(openai("gpt-5"), "user-123", {
addMemory: "always"
const modelWithMemory = withSupermemory(openai("gpt-5"), {
containerTag: "user-123",
customId: "conversation-456",
addMemory: "always",
})
```
</Note>
@ -58,19 +63,19 @@ 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(openai("gpt-4"), { containerTag: "user-123", customId: "conv-1", 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(openai("gpt-4"), { containerTag: "user-123", customId: "conv-1", mode: "query" })
```
**Full Mode** - Combines profile AND query-based search:
```typescript
const model = withSupermemory(openai("gpt-4"), "user-123", { mode: "full" })
const model = withSupermemory(openai("gpt-4"), { containerTag: "user-123", customId: "conv-1", mode: "full" })
```
### Custom Prompt Templates
@ -91,17 +96,21 @@ const claudePrompt = (data: MemoryPromptData) => `
</context>
`.trim()
const model = withSupermemory(anthropic("claude-3-sonnet"), "user-123", {
const model = withSupermemory(anthropic("claude-3-sonnet"), {
containerTag: "user-123",
customId: "conv-1",
mode: "full",
promptTemplate: claudePrompt
promptTemplate: claudePrompt,
})
```
### Verbose Logging
```typescript
const model = withSupermemory(openai("gpt-4"), "user-123", {
verbose: true
const model = withSupermemory(openai("gpt-4"), {
containerTag: "user-123",
customId: "conv-1",
verbose: true,
})
// Console output shows memory retrieval details
```
@ -113,8 +122,10 @@ If the Supermemory API returns an error, is unreachable, or retrieval hits the i
To **fail the call** when memory retrieval fails instead, set `skipMemoryOnError: false`:
```typescript
const model = withSupermemory(openai("gpt-5"), "user-123", {
skipMemoryOnError: false
const model = withSupermemory(openai("gpt-5"), {
containerTag: "user-123",
customId: "conv-1",
skipMemoryOnError: false,
})
```

View file

@ -228,7 +228,10 @@ ${result.searchResults?.results.map(m => m.memory).join('\n') || 'None'}
import { openai } from "@ai-sdk/openai"
// Profiles automatically injected
const model = withSupermemory(openai("gpt-4"), "user-123")
const model = withSupermemory(openai("gpt-4"), {
containerTag: "user-123",
customId: "conv-1",
})
const result = await generateText({
model,

View file

@ -352,8 +352,11 @@ import { generateText } from "ai"
import { withSupermemory } from "@supermemory/tools/ai-sdk"
import { openai } from "@ai-sdk/openai"
// One line setup - profiles automatically injected
const model = withSupermemory(openai("gpt-4"), "user-123")
// Simple setup - profiles automatically injected
const model = withSupermemory(openai("gpt-4"), {
containerTag: "user-123",
customId: "conv-1",
})
const result = await generateText({
model,

View file

@ -143,7 +143,10 @@ const result = await streamText({
// Option 2: Profile middleware (automatic context injection)
import { withSupermemory } from '@supermemory/tools/ai-sdk'
const modelWithMemory = withSupermemory(anthropic('claude-3-5-sonnet-20241022'), userId)
const modelWithMemory = withSupermemory(anthropic('claude-3-5-sonnet-20241022'), {
containerTag: userId,
customId: 'conversation-1',
})
const result = await generateText({
model: modelWithMemory,

View file

@ -13,17 +13,24 @@ async function testMiddleware() {
console.log("=== Middleware ===")
// Basic wrapper
const model = withSupermemory(openai("gpt-4"), "user-123")
const model = withSupermemory(openai("gpt-4"), {
containerTag: "user-123",
customId: "conv-1",
})
console.log("✓ withSupermemory basic")
// With addMemory option
const modelWithAdd = withSupermemory(openai("gpt-4"), "user-123", {
const modelWithAdd = withSupermemory(openai("gpt-4"), {
containerTag: "user-123",
customId: "conv-1",
addMemory: "always",
})
console.log("✓ withSupermemory with addMemory")
// With verbose logging
const modelVerbose = withSupermemory(openai("gpt-4"), "user-123", {
const modelVerbose = withSupermemory(openai("gpt-4"), {
containerTag: "user-123",
customId: "conv-1",
verbose: true,
})
console.log("✓ withSupermemory with verbose")
@ -32,17 +39,23 @@ async function testMiddleware() {
async function testSearchModes() {
console.log("\n=== Search Modes ===")
const profileModel = withSupermemory(openai("gpt-4"), "user-123", {
const profileModel = withSupermemory(openai("gpt-4"), {
containerTag: "user-123",
customId: "conv-1",
mode: "profile",
})
console.log("✓ mode: profile")
const queryModel = withSupermemory(openai("gpt-4"), "user-123", {
const queryModel = withSupermemory(openai("gpt-4"), {
containerTag: "user-123",
customId: "conv-1",
mode: "query",
})
console.log("✓ mode: query")
const fullModel = withSupermemory(openai("gpt-4"), "user-123", {
const fullModel = withSupermemory(openai("gpt-4"), {
containerTag: "user-123",
customId: "conv-1",
mode: "full",
})
console.log("✓ mode: full")
@ -61,14 +74,12 @@ async function testCustomPrompt() {
</context>
`.trim()
const model = withSupermemory(
anthropic("claude-3-sonnet-20240229"),
"user-123",
{
mode: "full",
promptTemplate: claudePrompt,
},
)
const model = withSupermemory(anthropic("claude-3-sonnet-20240229"), {
containerTag: "user-123",
customId: "conv-1",
mode: "full",
promptTemplate: claudePrompt,
})
console.log("✓ Custom prompt template")
}

View file

@ -66,27 +66,9 @@ import { generateText } from "ai"
import { withSupermemory } from "@supermemory/tools/ai-sdk"
import { openai } from "@ai-sdk/openai"
const modelWithMemory = withSupermemory(openai("gpt-5"), "user_id_life")
const result = await generateText({
model: modelWithMemory,
messages: [{ role: "user", content: "where do i live?" }],
})
console.log(result.text)
```
#### Conversation Grouping
Use the `conversationId` option to group messages into a single document for contextual memory generation:
```typescript
import { generateText } from "ai"
import { withSupermemory } from "@supermemory/tools/ai-sdk"
import { openai } from "@ai-sdk/openai"
const modelWithMemory = withSupermemory(openai("gpt-5"), "user_id_life", {
conversationId: "conversation-456"
const modelWithMemory = withSupermemory(openai("gpt-5"), {
containerTag: "user_id_life",
customId: "conversation-456",
})
const result = await generateText({
@ -106,8 +88,10 @@ import { generateText } from "ai"
import { withSupermemory } from "@supermemory/tools/ai-sdk"
import { openai } from "@ai-sdk/openai"
const modelWithMemory = withSupermemory(openai("gpt-5"), "user_id_life", {
verbose: true
const modelWithMemory = withSupermemory(openai("gpt-5"), {
containerTag: "user_id_life",
customId: "conversation-456",
verbose: true,
})
const result = await generateText({
@ -139,11 +123,16 @@ import { withSupermemory } from "@supermemory/tools/ai-sdk"
import { openai } from "@ai-sdk/openai"
// Uses profile mode by default - gets all user profile memories
const modelWithMemory = withSupermemory(openai("gpt-4"), "user-123")
const modelWithMemory = withSupermemory(openai("gpt-4"), {
containerTag: "user-123",
customId: "conversation-456",
})
// Explicitly specify profile mode
const modelWithProfile = withSupermemory(openai("gpt-4"), "user-123", {
mode: "profile"
const modelWithProfile = withSupermemory(openai("gpt-4"), {
containerTag: "user-123",
customId: "conversation-456",
mode: "profile",
})
const result = await generateText({
@ -158,8 +147,10 @@ import { generateText } from "ai"
import { withSupermemory } from "@supermemory/tools/ai-sdk"
import { openai } from "@ai-sdk/openai"
const modelWithQuery = withSupermemory(openai("gpt-4"), "user-123", {
mode: "query"
const modelWithQuery = withSupermemory(openai("gpt-4"), {
containerTag: "user-123",
customId: "conversation-456",
mode: "query",
})
const result = await generateText({
@ -174,8 +165,10 @@ import { generateText } from "ai"
import { withSupermemory } from "@supermemory/tools/ai-sdk"
import { openai } from "@ai-sdk/openai"
const modelWithFull = withSupermemory(openai("gpt-4"), "user-123", {
mode: "full"
const modelWithFull = withSupermemory(openai("gpt-4"), {
containerTag: "user-123",
customId: "conversation-456",
mode: "full",
})
const result = await generateText({
@ -194,8 +187,10 @@ import { generateText } from "ai"
import { withSupermemory } from "@supermemory/tools/ai-sdk"
import { openai } from "@ai-sdk/openai"
const modelWithAutoSave = withSupermemory(openai("gpt-4"), "user-123", {
addMemory: "always"
const modelWithAutoSave = withSupermemory(openai("gpt-4"), {
containerTag: "user-123",
customId: "conversation-456",
addMemory: "always",
})
const result = await generateText({
@ -205,17 +200,23 @@ const result = await generateText({
// This message will be automatically saved as a memory
```
**Never Save Memories (Default)** - Only retrieves memories without storing new ones:
**Never Save Memories** - Only retrieves memories without storing new ones:
```typescript
const modelWithNoSave = withSupermemory(openai("gpt-4"), "user-123")
const modelWithNoSave = withSupermemory(openai("gpt-4"), {
containerTag: "user-123",
customId: "conversation-456",
addMemory: "never", // explicit since default is now "always"
})
```
**Combined Options** - Use verbose logging with specific modes and memory storage:
```typescript
const modelWithOptions = withSupermemory(openai("gpt-4"), "user-123", {
const modelWithOptions = withSupermemory(openai("gpt-4"), {
containerTag: "user-123",
customId: "conversation-456",
mode: "profile",
addMemory: "always",
verbose: true
verbose: true,
})
```
@ -239,7 +240,9 @@ ${data.generalSearchMemories}
</user_memories>
`.trim()
const modelWithCustomPrompt = withSupermemory(openai("gpt-4"), "user-123", {
const modelWithCustomPrompt = withSupermemory(openai("gpt-4"), {
containerTag: "user-123",
customId: "conversation-456",
mode: "full",
promptTemplate: customPrompt,
})
@ -646,11 +649,12 @@ Without `strict: true`, optional fields like `includeFullDocs` and `limit` won't
### withSupermemory Middleware Options
The `withSupermemory` middleware accepts additional configuration options:
The `withSupermemory` middleware accepts a configuration object as the second argument:
```typescript
interface WithSupermemoryOptions {
conversationId?: string
containerTag: string
customId: string
verbose?: boolean
mode?: "profile" | "query" | "full"
addMemory?: "always" | "never"
@ -662,7 +666,8 @@ interface WithSupermemoryOptions {
}
```
- **conversationId**: Optional conversation ID to group messages into a single document for contextual memory generation
- **containerTag**: Required. The container tag/identifier for memory search (e.g., user ID, project ID)
- **customId**: Required. Custom ID to group messages into a single document for contextual memory generation
- **verbose**: Enable detailed logging of memory search and injection process (default: false)
- **mode**: Memory search mode - "profile" (default), "query", or "full"
- **addMemory**: Automatic memory storage mode - "always" or "never" (default: "never")

View file

@ -17,8 +17,8 @@ const DEFAULT_MEMORY_RETRIEVAL_TIMEOUT_MS = 5000
interface WrapVercelLanguageModelOptions {
/** The container tag/identifier for memory search (e.g., user ID, project ID) */
containerTag: string
/** Optional conversation ID to group messages for contextual memory generation */
conversationId?: string
/** Custom ID to group messages into a single document. Required. */
customId: string
/** Enable detailed logging of memory search and injection */
verbose?: boolean
/**
@ -77,10 +77,10 @@ interface WrapVercelLanguageModelOptions {
* @param model - The language model to wrap with supermemory capabilities (V2 or V3)
* @param options - Configuration options for Supermemory integration
* @param options.containerTag - Required. The container tag/identifier for memory search (e.g., user ID, project ID)
* @param options.conversationId - Optional conversation ID to group messages into a single document for contextual memory generation
* @param options.customId - Required. Custom ID to group messages into a single document for contextual memory generation
* @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.addMemory - Optional mode for memory search: "always", "never" (default: "always")
* @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")
* @param options.skipMemoryOnError - When memory retrieval fails or times out: `true` (default) continues without injected memories; `false` throws
@ -94,7 +94,7 @@ interface WrapVercelLanguageModelOptions {
*
* const modelWithMemory = withSupermemory(openai("gpt-4"), {
* containerTag: "user-123",
* conversationId: "conversation-456",
* customId: "conversation-456",
* mode: "full",
* addMemory: "always"
* })
@ -120,13 +120,19 @@ const wrapVercelLanguageModel = <T extends LanguageModel>(
)
}
if (!options.customId) {
throw new Error(
"customId is required — provide a non-empty string to group messages into a single document",
)
}
const ctx = createSupermemoryContext({
containerTag: options.containerTag,
apiKey: providedApiKey,
conversationId: options.conversationId,
customId: options.customId,
verbose: options.verbose ?? false,
mode: options.mode ?? "profile",
addMemory: options.addMemory ?? "never",
addMemory: options.addMemory ?? "always",
baseUrl: options.baseUrl,
promptTemplate: options.promptTemplate,
memoryRetrievalTimeoutMs: DEFAULT_MEMORY_RETRIEVAL_TIMEOUT_MS,
@ -181,7 +187,7 @@ const wrapVercelLanguageModel = <T extends LanguageModel>(
saveMemoryAfterResponse(
ctx.client,
ctx.containerTag,
ctx.conversationId,
ctx.customId,
assistantResponseText,
params,
ctx.logger,
@ -256,7 +262,7 @@ const wrapVercelLanguageModel = <T extends LanguageModel>(
saveMemoryAfterResponse(
ctx.client,
ctx.containerTag,
ctx.conversationId,
ctx.customId,
generatedText,
params,
ctx.logger,

View file

@ -12,32 +12,9 @@ import {
type PromptTemplate,
type MemoryMode,
} from "../shared"
import {
type LanguageModelCallOptions,
getLastUserMessage,
filterOutSupermemories,
} from "./util"
import { type LanguageModelCallOptions, getLastUserMessage } from "./util"
import { extractQueryText, injectMemoriesIntoParams } from "./memory-prompt"
const getConversationContent = (params: LanguageModelCallOptions) => {
return params.prompt
.filter((msg) => msg.role !== "system" && msg.role !== "tool")
.map((msg) => {
const role = msg.role === "user" ? "User" : "Assistant"
if (typeof msg.content === "string") {
return `${role}: ${filterOutSupermemories(msg.content)}`
}
const content = msg.content
.filter((c) => c.type === "text")
.map((c) => (c.type === "text" ? filterOutSupermemories(c.text) : ""))
.join(" ")
return `${role}: ${content}`
})
.join("\n\n")
}
const convertToConversationMessages = (
params: LanguageModelCallOptions,
assistantResponseText: string,
@ -99,58 +76,34 @@ 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.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", {
@ -167,8 +120,8 @@ interface SupermemoryMiddlewareOptions {
containerTag: string
/** Supermemory API key */
apiKey: string
/** Optional conversation ID to group messages for contextual memory generation */
conversationId?: string
/** Custom ID to group messages into a single document. Required. */
customId: string
/** Enable detailed logging of memory search and injection */
verbose?: boolean
/**
@ -196,7 +149,7 @@ interface SupermemoryMiddlewareContext {
client: Supermemory
logger: Logger
containerTag: string
conversationId?: string
customId: string
mode: MemoryMode
addMemory: "always" | "never"
normalizedBaseUrl: string
@ -216,10 +169,10 @@ export const createSupermemoryContext = (
const {
containerTag,
apiKey,
conversationId,
customId,
verbose = false,
mode = "profile",
addMemory = "never",
addMemory = "always",
baseUrl,
promptTemplate,
memoryRetrievalTimeoutMs,
@ -239,7 +192,7 @@ export const createSupermemoryContext = (
client,
logger,
containerTag,
conversationId,
customId,
mode,
addMemory,
normalizedBaseUrl,
@ -262,7 +215,7 @@ const makeTurnKey = (
): string => {
return MemoryCache.makeTurnKey(
ctx.containerTag,
ctx.conversationId,
ctx.customId,
ctx.mode,
userMessage,
)
@ -303,7 +256,7 @@ export const transformParamsWithMemory = async (
ctx.logger.info("Starting memory search", {
containerTag: ctx.containerTag,
conversationId: ctx.conversationId,
customId: ctx.customId,
mode: ctx.mode,
isNewTurn,
cacheHit: false,

View file

@ -4,6 +4,7 @@ import { openai } from "@ai-sdk/openai"
const modelWithMemory = withSupermemory(openai("gpt-5"), {
containerTag: "user_id_life",
customId: "conversation-123",
verbose: true,
mode: "query", // options are profile, query, full (default is profile)
addMemory: "always", // options are always, never (default is never)

View file

@ -3,6 +3,7 @@ import { withSupermemory } from "@supermemory/tools/ai-sdk"
const model = withSupermemory(gateway("google/gemini-2.5-flash"), {
containerTag: "user-1",
customId: "chat-session",
apiKey: process.env.SUPERMEMORY_API_KEY ?? "",
mode: "full",
addMemory: "always",

View file

@ -9,6 +9,7 @@ const gatewayModel = gateway("google/gemini-2.5-flash")
const supermemoryOptions = {
containerTag: SUPERMEMORY_USER_ID,
customId: "stream-session",
apiKey: process.env.SUPERMEMORY_API_KEY ?? "",
mode: "full" as const,
addMemory: "always" as const,

View file

@ -98,6 +98,7 @@ describe.skipIf(!shouldRunIntegration)(
const wrapped = withSupermemory(model, {
containerTag: INTEGRATION_CONFIG.containerTag,
customId: "test-generate",
apiKey: INTEGRATION_CONFIG.apiKey,
mode: "profile",
})
@ -122,14 +123,14 @@ 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(model, {
containerTag: INTEGRATION_CONFIG.containerTag,
customId,
apiKey: INTEGRATION_CONFIG.apiKey,
mode: "profile",
addMemory: "always",
conversationId,
})
await wrapped.doGenerate({
@ -160,17 +161,17 @@ 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(model, {
containerTag: INTEGRATION_CONFIG.containerTag,
customId,
apiKey: INTEGRATION_CONFIG.apiKey,
mode: "profile",
conversationId,
})
await wrapped.doGenerate({
@ -196,6 +197,7 @@ describe.skipIf(!shouldRunIntegration)(
const wrapped = withSupermemory(model, {
containerTag: INTEGRATION_CONFIG.containerTag,
customId: "test-stream",
apiKey: INTEGRATION_CONFIG.apiKey,
mode: "profile",
})
@ -228,14 +230,14 @@ 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(model, {
containerTag: INTEGRATION_CONFIG.containerTag,
customId,
apiKey: INTEGRATION_CONFIG.apiKey,
mode: "profile",
addMemory: "always",
conversationId,
})
const { stream } = await wrapped.doStream({
@ -273,6 +275,7 @@ describe.skipIf(!shouldRunIntegration)(
const wrapped = withSupermemory(model, {
containerTag: INTEGRATION_CONFIG.containerTag,
customId: "test-chunks",
apiKey: INTEGRATION_CONFIG.apiKey,
mode: "profile",
})
@ -311,6 +314,7 @@ describe.skipIf(!shouldRunIntegration)(
const wrapped = withSupermemory(model, {
containerTag: INTEGRATION_CONFIG.containerTag,
customId: "test-profile",
apiKey: INTEGRATION_CONFIG.apiKey,
mode: "profile",
})
@ -349,6 +353,7 @@ describe.skipIf(!shouldRunIntegration)(
const wrapped = withSupermemory(model, {
containerTag: INTEGRATION_CONFIG.containerTag,
customId: "test-query",
apiKey: INTEGRATION_CONFIG.apiKey,
mode: "query",
})
@ -387,6 +392,7 @@ describe.skipIf(!shouldRunIntegration)(
const wrapped = withSupermemory(model, {
containerTag: INTEGRATION_CONFIG.containerTag,
customId: "test-full",
apiKey: INTEGRATION_CONFIG.apiKey,
mode: "full",
})
@ -431,6 +437,7 @@ describe.skipIf(!shouldRunIntegration)(
const wrapped = withSupermemory(model, {
containerTag: INTEGRATION_CONFIG.containerTag,
customId: "test-template",
apiKey: INTEGRATION_CONFIG.apiKey,
mode: "profile",
promptTemplate: customTemplate,
@ -457,6 +464,7 @@ describe.skipIf(!shouldRunIntegration)(
const wrapped = withSupermemory(model, {
containerTag: INTEGRATION_CONFIG.containerTag,
customId: "test-verbose",
apiKey: INTEGRATION_CONFIG.apiKey,
mode: "profile",
verbose: true,
@ -483,6 +491,7 @@ describe.skipIf(!shouldRunIntegration)(
// Use the configured base URL (or default)
const wrapped = withSupermemory(model, {
containerTag: INTEGRATION_CONFIG.containerTag,
customId: "test-baseurl",
apiKey: INTEGRATION_CONFIG.apiKey,
mode: "profile",
baseUrl: INTEGRATION_CONFIG.baseUrl,
@ -522,6 +531,7 @@ describe.skipIf(!shouldRunIntegration)(
const wrapped = withSupermemory(model, {
containerTag: INTEGRATION_CONFIG.containerTag,
customId: "test-error",
apiKey: INTEGRATION_CONFIG.apiKey,
mode: "profile",
})
@ -544,6 +554,7 @@ describe.skipIf(!shouldRunIntegration)(
const wrapped = withSupermemory(model, {
containerTag: INTEGRATION_CONFIG.containerTag,
customId: "test-invalid-key",
apiKey: "invalid-api-key-12345",
mode: "profile",
})
@ -566,6 +577,7 @@ describe.skipIf(!shouldRunIntegration)(
const wrapped = withSupermemory(model, {
containerTag: INTEGRATION_CONFIG.containerTag,
customId: "test-invalid-strict",
apiKey: "invalid-api-key-12345",
mode: "profile",
skipMemoryOnError: false,

View file

@ -73,16 +73,41 @@ describe("Unit: withSupermemory", () => {
const mockModel = createMockLanguageModel()
expect(() => {
withSupermemory(mockModel, { containerTag: TEST_CONFIG.containerTag })
withSupermemory(mockModel, {
containerTag: TEST_CONFIG.containerTag,
customId: "test-id",
})
}).toThrow("SUPERMEMORY_API_KEY is not set")
})
it("should throw error if customId is missing or empty", () => {
process.env.SUPERMEMORY_API_KEY = "test-key"
const mockModel = createMockLanguageModel()
// omitted customId (plain JS caller)
expect(() => {
withSupermemory(mockModel, {
containerTag: TEST_CONFIG.containerTag,
} as any)
}).toThrow("customId is required")
// empty string
expect(() => {
withSupermemory(mockModel, {
containerTag: TEST_CONFIG.containerTag,
customId: "",
})
}).toThrow("customId is required")
})
it("should successfully create wrapped model with valid API key", () => {
process.env.SUPERMEMORY_API_KEY = "test-key"
const mockModel = createMockLanguageModel()
const wrappedModel = withSupermemory(mockModel, {
containerTag: TEST_CONFIG.containerTag,
customId: "test-id",
})
expect(wrappedModel).toBeDefined()
@ -103,6 +128,7 @@ describe("Unit: withSupermemory", () => {
const inner = Object.create(proto) as LanguageModelV2
const wrappedModel = withSupermemory(inner, {
containerTag: TEST_CONFIG.containerTag,
customId: "test-id",
})
expect(wrappedModel.specificationVersion).toBe("v2")
@ -129,6 +155,7 @@ describe("Unit: withSupermemory", () => {
const ctx = createSupermemoryContext({
containerTag: TEST_CONFIG.containerTag,
apiKey: TEST_CONFIG.apiKey,
customId: "test-id",
mode: "profile",
})
@ -144,7 +171,7 @@ describe("Unit: withSupermemory", () => {
await transformParamsWithMemory(params, ctx)
expect(ctx.memoryCache).toBeDefined()
const turnKey = `${TEST_CONFIG.containerTag}::profile:Hello`
const turnKey = `${TEST_CONFIG.containerTag}:test-id:profile:Hello`
const cachedMemories = ctx.memoryCache.get(turnKey)
expect(cachedMemories).toBeDefined()
expect(cachedMemories).toContain("Cached memory")
@ -161,6 +188,7 @@ describe("Unit: withSupermemory", () => {
const ctx = createSupermemoryContext({
containerTag: TEST_CONFIG.containerTag,
apiKey: TEST_CONFIG.apiKey,
customId: "test-id",
mode: "profile",
})
@ -233,6 +261,7 @@ describe("Unit: withSupermemory", () => {
const ctx = createSupermemoryContext({
containerTag: TEST_CONFIG.containerTag,
apiKey: TEST_CONFIG.apiKey,
customId: "test-id",
mode: "profile",
})
@ -293,6 +322,7 @@ describe("Unit: withSupermemory", () => {
const ctx = createSupermemoryContext({
containerTag: TEST_CONFIG.containerTag,
apiKey: TEST_CONFIG.apiKey,
customId: "test-id",
mode: "profile",
})
@ -314,6 +344,7 @@ describe("Unit: withSupermemory", () => {
const ctx = createSupermemoryContext({
containerTag: TEST_CONFIG.containerTag,
apiKey: TEST_CONFIG.apiKey,
customId: "test-id",
mode: "query",
})
@ -331,6 +362,7 @@ describe("Unit: withSupermemory", () => {
const ctx = createSupermemoryContext({
containerTag: TEST_CONFIG.containerTag,
apiKey: TEST_CONFIG.apiKey,
customId: "test-id",
mode: "query",
})
@ -358,6 +390,7 @@ describe("Unit: withSupermemory", () => {
const ctx = createSupermemoryContext({
containerTag: TEST_CONFIG.containerTag,
apiKey: TEST_CONFIG.apiKey,
customId: "test-id",
mode: "profile",
})
@ -420,6 +453,7 @@ describe("Unit: withSupermemory", () => {
const wrapped = withSupermemory(inner, {
containerTag: TEST_CONFIG.containerTag,
customId: "test-id",
apiKey: "k",
})
@ -443,6 +477,7 @@ describe("Unit: withSupermemory", () => {
const inner = createMockLanguageModel()
const wrapped = withSupermemory(inner, {
containerTag: TEST_CONFIG.containerTag,
customId: "test-id",
apiKey: "k",
skipMemoryOnError: false,
})
@ -483,6 +518,7 @@ describe("Unit: withSupermemory", () => {
const wrapped = withSupermemory(inner, {
containerTag: TEST_CONFIG.containerTag,
customId: "test-id",
apiKey: "k",
})