diff --git a/packages/tools/src/openai/index.ts b/packages/tools/src/openai/index.ts index a13f5ed8..54e98402 100644 --- a/packages/tools/src/openai/index.ts +++ b/packages/tools/src/openai/index.ts @@ -6,11 +6,13 @@ import { /** * Wraps an OpenAI client with SuperMemory middleware to automatically inject relevant memories - * into the system prompt based on the user's message content. + * into both Chat Completions and Responses APIs based on the user's input content. * - * This middleware searches the supermemory API for relevant memories using the container tag - * and user message, then either appends memories to an existing system prompt or creates - * a new system prompt with the memories. + * For Chat Completions API: Searches for memories using the user message content and injects + * them into the system prompt (appends to existing or creates new system prompt). + * + * For Responses API: Searches for memories using the input parameter and injects them into + * the instructions parameter (appends to existing or creates new instructions). * * @param openaiClient - The OpenAI client to wrap with SuperMemory middleware * @param containerTag - The container tag/identifier for memory search (e.g., user ID, project ID) @@ -20,7 +22,7 @@ import { * @param options.mode - Optional mode for memory search: "profile" (default), "query", or "full" * @param options.addMemory - Optional mode for memory addition: "always", "never" (default) * - * @returns An OpenAI client with SuperMemory middleware injected + * @returns An OpenAI client with SuperMemory middleware injected for both Chat Completions and Responses APIs * * @example * ```typescript @@ -37,13 +39,20 @@ import { * addMemory: "always" * }) * - * // Use normally - memories will be automatically injected - * const response = await openaiWithSupermemory.chat.completions.create({ + * // Use with Chat Completions API - memories injected into system prompt + * const chatResponse = await openaiWithSupermemory.chat.completions.create({ * model: "gpt-4", * messages: [ * { role: "user", content: "What's my favorite programming language?" } * ] * }) + * + * // Use with Responses API - memories injected into instructions + * const response = await openaiWithSupermemory.responses.create({ + * model: "gpt-4o", + * instructions: "You are a helpful coding assistant", + * input: "What's my favorite programming language?" + * }) * ``` * * @throws {Error} When SUPERMEMORY_API_KEY environment variable is not set diff --git a/packages/tools/src/openai/middleware.ts b/packages/tools/src/openai/middleware.ts index 8d83874d..a438e050 100644 --- a/packages/tools/src/openai/middleware.ts +++ b/packages/tools/src/openai/middleware.ts @@ -338,7 +338,132 @@ export function createOpenAIMiddleware( apiKey: process.env.SUPERMEMORY_API_KEY, }) + const conversationId = options?.conversationId + const mode = options?.mode ?? "profile" + const addMemory = options?.addMemory ?? "never" + const originalCreate = openaiClient.chat.completions.create + const originalResponsesCreate = openaiClient.responses?.create + + /** + * Formats memories for injection into Responses API instructions. + * + * Searches for relevant memories and formats them for inclusion in the + * instructions parameter of the Responses API. + * + * @param input - The input text from the Responses API call + * @param containerTag - The container tag for memory search + * @param logger - Logger instance + * @param mode - Memory search mode + * @returns Formatted memories string for instructions + */ + const getMemoriesForInstructions = async ( + input: string, + containerTag: string, + logger: Logger, + mode: "profile" | "query" | "full", + ) => { + const queryText = mode !== "profile" ? input : "" + + const memoriesResponse = await supermemoryProfileSearch( + containerTag, + queryText, + ) + + const memoryCountStatic = memoriesResponse.profile.static?.length || 0 + const memoryCountDynamic = memoriesResponse.profile.dynamic?.length || 0 + + logger.info("Memory search completed for Responses API", { + containerTag, + memoryCountStatic, + memoryCountDynamic, + queryText: + queryText.substring(0, 100) + (queryText.length > 100 ? "..." : ""), + mode, + }) + + const profileData = + mode !== "query" + ? convertProfileToMarkdown({ + profile: { + static: memoriesResponse.profile.static?.map((item) => item.memory), + dynamic: memoriesResponse.profile.dynamic?.map( + (item) => item.memory, + ), + }, + searchResults: { + results: memoriesResponse.searchResults.results.map((item) => ({ + memory: item.memory, + })) as [{ memory: string }], + }, + }) + : "" + const searchResultsMemories = + mode !== "profile" + ? `Search results for user's input: \n${memoriesResponse.searchResults.results + .map((result) => `- ${result.memory}`) + .join("\n")}` + : "" + + const memories = `${profileData}\n${searchResultsMemories}`.trim() + + if (memories) { + logger.debug("Memory content preview for Responses API", { + content: memories, + fullLength: memories.length, + }) + } + + return memories + } + + const createResponsesWithMemory = async ( + params: Parameters[0], + ) => { + if (!originalResponsesCreate) { + throw new Error("Responses API is not available in this OpenAI client version") + } + + const input = typeof params.input === "string" ? params.input : "" + + if (addMemory === "always" && input?.trim()) { + const content = conversationId + ? `Input: ${input}` + : input + const customId = conversationId + ? `conversation:${conversationId}` + : undefined + + addMemoryTool(client, containerTag, content, customId, logger) + } + + if (mode !== "profile" && !input) { + logger.debug("No input found for Responses API, skipping memory search") + return originalResponsesCreate.call(openaiClient.responses, params) + } + + logger.info("Starting memory search for Responses API", { + containerTag, + conversationId, + mode, + }) + + const memories = await getMemoriesForInstructions( + input, + containerTag, + logger, + mode, + ) + + const enhancedInstructions = memories + ? `${params.instructions || ""}\n\n${memories}`.trim() + : params.instructions + + return originalResponsesCreate.call(openaiClient.responses, { + ...params, + instructions: enhancedInstructions, + }) + } const createWithMemory = async ( params: OpenAI.Chat.Completions.ChatCompletionCreateParams, @@ -389,5 +514,11 @@ export function createOpenAIMiddleware( openaiClient.chat.completions.create = createWithMemory as typeof originalCreate + // Wrap Responses API if available + if (originalResponsesCreate) { + openaiClient.responses.create = + createResponsesWithMemory as typeof originalResponsesCreate + } + return openaiClient }