mirror of
https://github.com/supermemoryai/supermemory.git
synced 2026-11-03 07:25:47 +00:00
Merge 82ef13e720 into 4d86b728dd
This commit is contained in:
commit
09174cafcc
2 changed files with 143 additions and 3 deletions
121
packages/tools/src/openai/middleware.test.ts
Normal file
121
packages/tools/src/openai/middleware.test.ts
Normal 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()
|
||||
})
|
||||
})
|
||||
|
|
@ -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,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue