feat(tools): apiKey option, type re-exports, and two reliability fixes (#1594)
Some checks failed
Publish Tools / publish (push) Has been cancelled

Cherry-picks four contributor PRs for `@supermemory/tools` onto one branch, and bumps the package to 2.2.0.

- #1244 (@rajarshidattapy): `withSupermemory` accepts `options.apiKey` instead of only reading `SUPERMEMORY_API_KEY`, matching the Vercel, Mastra and Voltagent integrations. Unblocks secrets managers, edge runtimes and per-request keys.
- #1574 (@Agnik47): re-exports `PromptTemplate`, `MemoryPromptData` and `WithSupermemoryOptions` from `ai-sdk`. `./vercel` is not a published subpath, so the documented custom-template example did not compile.
- #1488 (@abhinav7x94): malformed tool-call JSON returns an error result instead of throwing out of the request.
- #1507 (@abhinav7x94): VoltAgent `onEnd` awaits the conversation save, which was fire-and-forget and could be dropped when a serverless runtime tore down.

Dropped the `middleware.test.ts` added by #1244. Note that editing `packages/tools/package.json` triggers the npm publish workflow on merge.

Co-Authored-By: rajarshidattapy <138959719+rajarshidattapy@users.noreply.github.com>
Co-Authored-By: Agnik47 <140933190+Agnik47@users.noreply.github.com>
Co-Authored-By: abhinav7x94 <204053250+abhinav7x94@users.noreply.github.com>
This commit is contained in:
MaheshtheDev 2026-08-24 22:39:55 +00:00
parent f051af098e
commit e4afc770be
8 changed files with 53 additions and 22 deletions

View file

@ -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",

View file

@ -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",

View file

@ -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"

View file

@ -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(

View file

@ -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<SupermemoryProfileSearch> => {
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)

View file

@ -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<string> {
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":

View file

@ -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",

View file

@ -448,7 +448,7 @@ const convertToConversationMessages = (
}
/**
* Saves conversation to Supermemory (fire-and-forget).
* Saves conversation to Supermemory.
*/
export const saveConversation = async (
messages: VoltAgentMessage[],