diff --git a/bun.lock b/bun.lock index d55c8237..f7c89869 100644 --- a/bun.lock +++ b/bun.lock @@ -336,7 +336,7 @@ }, "packages/tools": { "name": "@supermemory/tools", - "version": "2.1.1", + "version": "2.2.0", "dependencies": { "@ai-sdk/anthropic": "^2.0.25", "@ai-sdk/openai": "^2.0.23", diff --git a/packages/tools/package.json b/packages/tools/package.json index 59289f3d..6df7f9a9 100644 --- a/packages/tools/package.json +++ b/packages/tools/package.json @@ -1,7 +1,7 @@ { "name": "@supermemory/tools", "type": "module", - "version": "2.1.1", + "version": "2.2.0", "description": "Memory tools for AI SDK, OpenAI, Voltagent and Mastra with supermemory", "scripts": { "build": "tsdown", diff --git a/packages/tools/src/ai-sdk.ts b/packages/tools/src/ai-sdk.ts index f8d88154..40290baf 100644 --- a/packages/tools/src/ai-sdk.ts +++ b/packages/tools/src/ai-sdk.ts @@ -372,4 +372,10 @@ export function supermemoryTools( } } -export { withSupermemory } from "./vercel" +// `./vercel` is not a published subpath, so this is the only way consumers reach the middleware types. +export { + withSupermemory, + type WithSupermemoryOptions, + type PromptTemplate, + type MemoryPromptData, +} from "./vercel" diff --git a/packages/tools/src/openai/index.ts b/packages/tools/src/openai/index.ts index 8923b652..b436e078 100644 --- a/packages/tools/src/openai/index.ts +++ b/packages/tools/src/openai/index.ts @@ -1,4 +1,5 @@ import type OpenAI from "openai" +import { validateApiKey } from "../shared" import { createOpenAIMiddleware, type OpenAIMiddlewareOptions, @@ -21,6 +22,7 @@ import { * @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" (default), "query", or "full" * @param options.addMemory - Optional mode for memory addition: "always" (default), "never" + * @param options.apiKey - Optional Supermemory API key to use instead of the SUPERMEMORY_API_KEY environment variable * * @returns An OpenAI client with SuperMemory middleware injected for both Chat Completions and Responses APIs * @@ -56,16 +58,14 @@ import { * }) * ``` * - * @throws {Error} When SUPERMEMORY_API_KEY environment variable is not set + * @throws {Error} When neither `options.apiKey` nor `process.env.SUPERMEMORY_API_KEY` are set * @throws {Error} When supermemory API request fails */ export function withSupermemory( openaiClient: OpenAI, options: OpenAIMiddlewareOptions, ) { - if (!process.env.SUPERMEMORY_API_KEY) { - throw new Error("SUPERMEMORY_API_KEY is not set") - } + validateApiKey(options.apiKey) if (!options.containerTag) { throw new Error( diff --git a/packages/tools/src/openai/middleware.ts b/packages/tools/src/openai/middleware.ts index c9b8b4b8..5ac50402 100644 --- a/packages/tools/src/openai/middleware.ts +++ b/packages/tools/src/openai/middleware.ts @@ -1,6 +1,7 @@ import type OpenAI from "openai" import Supermemory from "supermemory" import { addConversation } from "../conversations-client" +import { validateApiKey } from "../shared" import { deduplicateMemoriesForMode } from "../tools-shared" import { createLogger, type Logger } from "../vercel/logger" import { convertProfileToMarkdown } from "../vercel/util" @@ -20,6 +21,7 @@ export interface OpenAIMiddlewareOptions { mode?: "profile" | "query" | "full" addMemory?: "always" | "never" baseUrl?: string + apiKey?: string } interface SupermemoryProfileSearch { @@ -75,22 +77,25 @@ const getLastUserMessage = ( * * @param containerTag - The container tag/identifier for memory search (e.g., user ID, project ID) * @param queryText - Optional query text to search for specific memories. If empty, returns all profile memories + * @param baseUrl - The Supermemory API base URL + * @param apiKey - The Supermemory API key used to authenticate the request * @returns Promise that resolves to the SuperMemory profile search response * @throws {Error} When the API request fails or returns an error status * * @example * ```typescript * // Search with query - * const results = await supermemoryProfileSearch("user-123", "favorite programming language") + * const results = await supermemoryProfileSearch("user-123", "favorite programming language", baseUrl, apiKey) * * // Get all profile memories - * const profile = await supermemoryProfileSearch("user-123", "") + * const profile = await supermemoryProfileSearch("user-123", "", baseUrl, apiKey) * ``` */ const supermemoryProfileSearch = async ( containerTag: string, queryText: string, baseUrl: string, + apiKey: string, ): Promise => { const payload = queryText ? JSON.stringify({ @@ -106,7 +111,7 @@ const supermemoryProfileSearch = async ( method: "POST", headers: { "Content-Type": "application/json", - Authorization: `Bearer ${process.env.SUPERMEMORY_API_KEY}`, + Authorization: `Bearer ${apiKey}`, }, body: payload, }) @@ -138,6 +143,8 @@ const supermemoryProfileSearch = async ( * @param containerTag - The container tag/identifier for memory search * @param logger - Logger instance for debugging and info output * @param mode - Memory search mode: "profile" (all memories), "query" (search-based), or "full" (both) + * @param baseUrl - The Supermemory API base URL + * @param apiKey - The Supermemory API key used to authenticate the request * @returns Promise that resolves to enhanced messages with memory-injected system prompt * * @example @@ -150,7 +157,9 @@ const supermemoryProfileSearch = async ( * messages, * "user-123", * logger, - * "full" + * "full", + * baseUrl, + * apiKey * ) * // Returns messages with system prompt containing relevant memories * ``` @@ -161,6 +170,7 @@ const addSystemPrompt = async ( logger: Logger, mode: "profile" | "query" | "full", baseUrl: string, + apiKey: string, ) => { const systemPromptExists = messages.some((msg) => msg.role === "system") @@ -170,6 +180,7 @@ const addSystemPrompt = async ( containerTag, queryText, baseUrl, + apiKey, ) const memoryCountStatic = memoriesResponse.profile.static?.length || 0 @@ -400,8 +411,9 @@ const addMemoryTool = async ( * @param options.verbose - Enable detailed logging of memory operations (default: false) * @param options.mode - Memory search mode: "profile" (all memories), "query" (search-based), or "full" (both) (default: "profile") * @param options.addMemory - Automatic memory storage mode: "always" or "never" (default: "always") + * @param options.apiKey - Supermemory API key to use instead of the SUPERMEMORY_API_KEY environment variable * @returns Object with `wrapClient` and `createClient` methods - * @throws {Error} When SUPERMEMORY_API_KEY environment variable is not set + * @throws {Error} When neither `options.apiKey` nor `process.env.SUPERMEMORY_API_KEY` are set * * @example * ```typescript @@ -421,8 +433,9 @@ export function createOpenAIMiddleware( ) { const logger = createLogger(options?.verbose ?? false) const baseUrl = normalizeBaseUrl(options?.baseUrl) + const apiKey = validateApiKey(options?.apiKey) const client = new Supermemory({ - apiKey: process.env.SUPERMEMORY_API_KEY, + apiKey, ...(baseUrl !== "https://api.supermemory.ai" ? { baseURL: baseUrl } : {}), }) @@ -457,6 +470,7 @@ export function createOpenAIMiddleware( containerTag, queryText, baseUrl, + apiKey, ) const memoryCountStatic = memoriesResponse.profile.static?.length || 0 @@ -615,7 +629,7 @@ export function createOpenAIMiddleware( memoryCustomId, logger, messages, - process.env.SUPERMEMORY_API_KEY, + apiKey, baseUrl, ), ) @@ -623,7 +637,7 @@ export function createOpenAIMiddleware( } operations.push( - addSystemPrompt(messages, containerTag, logger, mode, baseUrl), + addSystemPrompt(messages, containerTag, logger, mode, baseUrl, apiKey), ) const results = await Promise.all(operations) diff --git a/packages/tools/src/openai/tools.ts b/packages/tools/src/openai/tools.ts index 4695c920..1ce23cb6 100644 --- a/packages/tools/src/openai/tools.ts +++ b/packages/tools/src/openai/tools.ts @@ -552,6 +552,14 @@ export function getToolDefinitions(): OpenAI.Chat.Completions.ChatCompletionTool ] } +function parseToolArguments(argumentsJson: string) { + try { + return { success: true as const, value: JSON.parse(argumentsJson) } + } catch { + return { success: false as const } + } +} + /** * Execute a tool call based on the function name and arguments */ @@ -565,7 +573,14 @@ export function createToolCallExecutor( toolCall: OpenAI.Chat.Completions.ChatCompletionMessageToolCall, ): Promise { const functionName = toolCall.function.name - const args = JSON.parse(toolCall.function.arguments) + const parsed = parseToolArguments(toolCall.function.arguments) + if (!parsed.success) { + return JSON.stringify({ + success: false, + error: `Invalid JSON arguments for ${functionName}`, + }) + } + const args = parsed.value switch (functionName) { case "searchMemories": diff --git a/packages/tools/src/voltagent/hooks.ts b/packages/tools/src/voltagent/hooks.ts index 87c78831..027ca002 100644 --- a/packages/tools/src/voltagent/hooks.ts +++ b/packages/tools/src/voltagent/hooks.ts @@ -129,11 +129,7 @@ export function createSupermemoryHooks( return } - saveConversation(messages, ctx).catch((error) => { - ctx.logger.error("Background conversation save failed", { - error: error instanceof Error ? error.message : "Unknown error", - }) - }) + await saveConversation(messages, ctx) } catch (error) { ctx.logger.error("Error in onEnd", { error: error instanceof Error ? error.message : "Unknown error", diff --git a/packages/tools/src/voltagent/middleware.ts b/packages/tools/src/voltagent/middleware.ts index bf771726..85b90364 100644 --- a/packages/tools/src/voltagent/middleware.ts +++ b/packages/tools/src/voltagent/middleware.ts @@ -448,7 +448,7 @@ const convertToConversationMessages = ( } /** - * Saves conversation to Supermemory (fire-and-forget). + * Saves conversation to Supermemory. */ export const saveConversation = async ( messages: VoltAgentMessage[],