From c76d27f284c1885a736c4f9ad2b3f60da7ed807d Mon Sep 17 00:00:00 2001 From: sreedharsreeram <141047751+sreedharsreeram@users.noreply.github.com> Date: Fri, 24 Apr 2026 23:43:16 +0000 Subject: [PATCH] custom id implementation (#877) --- README.md | 2 +- apps/docs/ai-sdk/overview.mdx | 11 ++- apps/docs/ai-sdk/user-profiles.mdx | 74 ++++++++++----- apps/docs/install.md | 5 +- apps/docs/integrations/ai-sdk.mdx | 35 ++++--- apps/docs/user-profiles.mdx | 5 +- apps/docs/user-profiles/examples.mdx | 7 +- apps/docs/vibe-coding.mdx | 5 +- .../docs-test/tests/integrations/ai-sdk.ts | 39 +++++--- packages/tools/README.md | 85 +++++++++-------- packages/tools/src/vercel/index.ts | 24 +++-- packages/tools/src/vercel/middleware.ts | 93 +++++-------------- packages/tools/test/ai-sdk-test.ts | 1 + .../tools/test/chatapp/app/api/chat/route.ts | 1 + .../test/chatapp/app/api/stream/route.ts | 1 + .../test/with-supermemory/integration.test.ts | 26 ++++-- .../tools/test/with-supermemory/unit.test.ts | 40 +++++++- 17 files changed, 266 insertions(+), 188 deletions(-) diff --git a/README.md b/README.md index 9701d0e2..a134afad 100644 --- a/README.md +++ b/README.md @@ -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"; diff --git a/apps/docs/ai-sdk/overview.mdx b/apps/docs/ai-sdk/overview.mdx index 0c9a48f4..168be519 100644 --- a/apps/docs/ai-sdk/overview.mdx +++ b/apps/docs/ai-sdk/overview.mdx @@ -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", }) ``` diff --git a/apps/docs/ai-sdk/user-profiles.mdx b/apps/docs/ai-sdk/user-profiles.mdx index df8c5430..6d85772e 100644 --- a/apps/docs/ai-sdk/user-profiles.mdx +++ b/apps/docs/ai-sdk/user-profiles.mdx @@ -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", }) ``` @@ -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} `.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 ```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({ diff --git a/apps/docs/install.md b/apps/docs/install.md index 665ccc16..78830cc5 100644 --- a/apps/docs/install.md +++ b/apps/docs/install.md @@ -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, diff --git a/apps/docs/integrations/ai-sdk.mdx b/apps/docs/integrations/ai-sdk.mdx index 30142040..c71a1ec1 100644 --- a/apps/docs/integrations/ai-sdk.mdx +++ b/apps/docs/integrations/ai-sdk.mdx @@ -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", }) ``` @@ -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) => ` `.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, }) ``` diff --git a/apps/docs/user-profiles.mdx b/apps/docs/user-profiles.mdx index ec9ff57d..e2b3ae46 100644 --- a/apps/docs/user-profiles.mdx +++ b/apps/docs/user-profiles.mdx @@ -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, diff --git a/apps/docs/user-profiles/examples.mdx b/apps/docs/user-profiles/examples.mdx index aa3b796b..bd64b9da 100644 --- a/apps/docs/user-profiles/examples.mdx +++ b/apps/docs/user-profiles/examples.mdx @@ -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, diff --git a/apps/docs/vibe-coding.mdx b/apps/docs/vibe-coding.mdx index bb5cd0e9..def08fdf 100644 --- a/apps/docs/vibe-coding.mdx +++ b/apps/docs/vibe-coding.mdx @@ -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, diff --git a/packages/docs-test/tests/integrations/ai-sdk.ts b/packages/docs-test/tests/integrations/ai-sdk.ts index f6d4f1d2..c431a228 100644 --- a/packages/docs-test/tests/integrations/ai-sdk.ts +++ b/packages/docs-test/tests/integrations/ai-sdk.ts @@ -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() { `.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") } diff --git a/packages/tools/README.md b/packages/tools/README.md index 839a5904..cf09a4c4 100644 --- a/packages/tools/README.md +++ b/packages/tools/README.md @@ -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} `.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") diff --git a/packages/tools/src/vercel/index.ts b/packages/tools/src/vercel/index.ts index 582b46af..3dbdcccd 100644 --- a/packages/tools/src/vercel/index.ts +++ b/packages/tools/src/vercel/index.ts @@ -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 = ( ) } + 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 = ( saveMemoryAfterResponse( ctx.client, ctx.containerTag, - ctx.conversationId, + ctx.customId, assistantResponseText, params, ctx.logger, @@ -256,7 +262,7 @@ const wrapVercelLanguageModel = ( saveMemoryAfterResponse( ctx.client, ctx.containerTag, - ctx.conversationId, + ctx.customId, generatedText, params, ctx.logger, diff --git a/packages/tools/src/vercel/middleware.ts b/packages/tools/src/vercel/middleware.ts index 38d50688..144bd7df 100644 --- a/packages/tools/src/vercel/middleware.ts +++ b/packages/tools/src/vercel/middleware.ts @@ -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 => { - 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, diff --git a/packages/tools/test/ai-sdk-test.ts b/packages/tools/test/ai-sdk-test.ts index 19bb272d..97d30d30 100644 --- a/packages/tools/test/ai-sdk-test.ts +++ b/packages/tools/test/ai-sdk-test.ts @@ -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) diff --git a/packages/tools/test/chatapp/app/api/chat/route.ts b/packages/tools/test/chatapp/app/api/chat/route.ts index ded72745..22c3b446 100644 --- a/packages/tools/test/chatapp/app/api/chat/route.ts +++ b/packages/tools/test/chatapp/app/api/chat/route.ts @@ -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", diff --git a/packages/tools/test/chatapp/app/api/stream/route.ts b/packages/tools/test/chatapp/app/api/stream/route.ts index a86c3941..fb784f91 100644 --- a/packages/tools/test/chatapp/app/api/stream/route.ts +++ b/packages/tools/test/chatapp/app/api/stream/route.ts @@ -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, diff --git a/packages/tools/test/with-supermemory/integration.test.ts b/packages/tools/test/with-supermemory/integration.test.ts index 13ea59c6..d2ffa84d 100644 --- a/packages/tools/test/with-supermemory/integration.test.ts +++ b/packages/tools/test/with-supermemory/integration.test.ts @@ -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, diff --git a/packages/tools/test/with-supermemory/unit.test.ts b/packages/tools/test/with-supermemory/unit.test.ts index 8461fb97..2653b345 100644 --- a/packages/tools/test/with-supermemory/unit.test.ts +++ b/packages/tools/test/with-supermemory/unit.test.ts @@ -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", })