This commit is contained in:
Abhay Singh 2026-08-04 19:51:36 -07:00 • committed by GitHub
commit 09174cafcc
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 143 additions and 3 deletions

View file

@ -0,0 +1,121 @@
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"
// Mock the Supermemory SDK and the conversations client so the middleware's
// memory-save routing can be asserted without network access.
const { clientAddMock, addConversationMock } = vi.hoisted(() => ({
clientAddMock: vi.fn(),
addConversationMock: vi.fn(),
}))
vi.mock("supermemory", () => ({
default: class MockSupermemory {
add = clientAddMock
},
}))
vi.mock("../conversations-client", () => ({
addConversation: addConversationMock,
}))
import { createOpenAIMiddleware } from "./middleware"
const EMPTY_PROFILE = {
profile: { static: [], dynamic: [] },
searchResults: { results: [] },
}
function makeFakeOpenAI() {
return {
chat: {
completions: {
create: vi.fn().mockResolvedValue({ id: "chatcmpl-1" }),
},
},
responses: {
create: vi.fn().mockResolvedValue({ id: "resp-1" }),
},
}
}
describe("createOpenAIMiddleware Responses API memory save", () => {
beforeEach(() => {
clientAddMock.mockReset().mockResolvedValue({ id: "mem-1" })
addConversationMock.mockReset().mockResolvedValue({ id: "conv-doc-1" })
process.env.SUPERMEMORY_API_KEY = "sm_test_key"
vi.stubGlobal(
"fetch",
vi.fn(async () => ({
ok: true,
json: async () => EMPTY_PROFILE,
})),
)
})
afterEach(() => {
vi.unstubAllGlobals()
})
it("routes a customId save through /v4/conversations like the chat path", async () => {
const openai = makeFakeOpenAI()
// biome-ignore lint/suspicious/noExplicitAny: minimal OpenAI stub for the wrapped surface
createOpenAIMiddleware(openai as any, "user-1", {
containerTag: "user-1",
customId: "conv-1",
addMemory: "always",
mode: "query",
})
await openai.responses.create({ input: "I love hiking", instructions: "" })
expect(addConversationMock).toHaveBeenCalledTimes(1)
const payload = addConversationMock.mock.calls[0]?.[0]
expect(payload.conversationId).toBe("conv-1")
expect(payload.messages).toEqual([
{ role: "user", content: "I love hiking" },
])
// The raw client.add fallback (which would have stored the internal
// "conversation:" prefix as the customId) must not fire.
expect(clientAddMock).not.toHaveBeenCalled()
})
it("never stores the internal conversation: prefix in the add fallback", async () => {
const openai = makeFakeOpenAI()
// biome-ignore lint/suspicious/noExplicitAny: minimal OpenAI stub for the wrapped surface
createOpenAIMiddleware(openai as any, "user-1", {
containerTag: "user-1",
customId: "conv-1",
addMemory: "always",
mode: "query",
})
// Without an API key the conversation branch cannot run, so the save
// falls through to client.add — the stored customId must be the
// user's configured id, not the prefixed routing marker.
delete process.env.SUPERMEMORY_API_KEY
await openai.responses.create({ input: "I love hiking", instructions: "" })
expect(addConversationMock).not.toHaveBeenCalled()
expect(clientAddMock).toHaveBeenCalledTimes(1)
expect(clientAddMock.mock.calls[0]?.[0]?.customId).toBe("conv-1")
})
it("saves without a customId via plain client.add", async () => {
const openai = makeFakeOpenAI()
// biome-ignore lint/suspicious/noExplicitAny: minimal OpenAI stub for the wrapped surface
createOpenAIMiddleware(openai as any, "user-1", {
containerTag: "user-1",
addMemory: "always",
mode: "query",
// biome-ignore lint/suspicious/noExplicitAny: exercising the optional-customId path
} as any)
await openai.responses.create({ input: "I love hiking", instructions: "" })
expect(addConversationMock).not.toHaveBeenCalled()
expect(clientAddMock).toHaveBeenCalledTimes(1)
const arg = clientAddMock.mock.calls[0]?.[0]
expect(arg.content).toBe("I love hiking")
expect(arg.customId).toBeUndefined()
})
})

View file

@ -367,11 +367,13 @@ const addMemoryTool = async (
return
}
// Fallback to old behavior for non-conversation memories
// Fallback to old behavior for non-conversation memories. The
// "conversation:" prefix is an internal routing marker — never let
// it reach the stored customId (colons fail customId validation).
const response = await client.add({
content,
containerTags: [containerTag],
customId,
customId: customId?.replace(/^conversation:/, ""),
})
logger.info("Memory saved successfully", {
@ -548,9 +550,26 @@ export function createOpenAIMiddleware(
if (addMemory === "always" && input?.trim()) {
const content = customId ? `Input: ${input}` : input
const memoryCustomId = customId ? `conversation:${customId}` : undefined
// Mirror the chat path: hand the input over as messages together
// with the credentials so a configured customId routes through
// /v4/conversations. Without these, addMemoryTool fell through to
// client.add() with the internal "conversation:" prefix baked
// into the stored customId — which the API rejects, so Responses
// API memory saves silently failed whenever customId was set.
const inputAsMessages: OpenAI.Chat.Completions.ChatCompletionMessageParam[] =
[{ role: "user", content: input }]
operations.push(
addMemoryTool(client, containerTag, content, memoryCustomId, logger),
addMemoryTool(
client,
containerTag,
content,
memoryCustomId,
logger,
customId ? inputAsMessages : undefined,
process.env.SUPERMEMORY_API_KEY,
baseUrl,
),
)
}