fix(tools): stop forcing empty instructions on Responses requests

The Responses middleware always set `instructions` on the forwarded
request, even when the caller never passed one and there were no
memories to inject. `replaceMemoryContext("", "")` returns `""`, so
those requests went out with `instructions: ""`.

Sending the field at all tells the Responses API to drop the
instructions carried over from a `previous_response_id` turn, so an
empty string silently wipes the caller's system prompt mid-conversation
instead of leaving it in place.

The no-input early return in the same function already gets this right —
it spreads `instructions` only when the caller supplied a string. Apply
the same rule to the main path, while still injecting when there are
memories to add.

Adds three regression tests to the existing OpenAI middleware unit
suite, which CI already runs on changes to packages/tools.
This commit is contained in:
Agnik47 2026-09-04 04:12:14 +05:30
parent 4d8a4ebfdd
commit 66c05271a1
2 changed files with 108 additions and 1 deletions

View file

@ -970,13 +970,22 @@ export function createOpenAIMiddleware(
: ""
}
// Sending `instructions` at all tells the Responses API to drop whatever a
// `previous_response_id` turn carried over, so an empty result must leave
// the caller's own field untouched rather than blank it. Matches the
// no-input early return above.
const instructionsOverride =
enhancedInstructions || typeof params.instructions === "string"
? { instructions: enhancedInstructions }
: {}
return {
request: originalResponsesCreate.call(
openaiClient.responses,
{
...params,
input: cleanedInput,
instructions: enhancedInstructions,
...instructionsOverride,
},
requestOptions,
),

View file

@ -63,3 +63,101 @@ describe("OpenAI middleware memory context", () => {
).toHaveLength(1)
})
})
describe("OpenAI Responses middleware instructions", () => {
const originalApiKey = process.env.SUPERMEMORY_API_KEY
beforeEach(() => {
process.env.SUPERMEMORY_API_KEY = "sm_test_key"
})
afterEach(() => {
if (originalApiKey === undefined) delete process.env.SUPERMEMORY_API_KEY
else process.env.SUPERMEMORY_API_KEY = originalApiKey
vi.unstubAllGlobals()
})
const stubMemoryFetch = (staticMemories: string[]) => {
vi.stubGlobal(
"fetch",
vi.fn().mockResolvedValue({
ok: true,
json: async () => ({
profile: {
static: staticMemories.map((memory) => ({ memory })),
dynamic: [],
},
searchResults: { results: [] },
}),
}),
)
}
const wrapResponsesClient = () => {
const originalCreate = vi.fn(() =>
Object.assign(Promise.resolve({ output: [] }), {
asResponse: async () => new Response(),
}),
)
const client = {
chat: { completions: { create: vi.fn() } },
responses: { create: originalCreate },
} as unknown as OpenAI
const wrapped = withSupermemory(client, {
containerTag: "user-a",
customId: "conversation-a",
mode: "profile",
addMemory: "never",
})
return { originalCreate, wrapped }
}
it("leaves instructions absent when the caller sent none and there is nothing to inject", async () => {
stubMemoryFetch([])
const { originalCreate, wrapped } = wrapResponsesClient()
await wrapped.responses.create({
model: "gpt-4o-mini",
input: "What do you remember?",
previous_response_id: "resp_123",
})
// An empty `instructions` still counts as present, and the Responses API
// drops the previous turn's instructions whenever the field is sent.
const forwarded = originalCreate.mock.calls[0]?.[0]
expect("instructions" in forwarded).toBe(false)
expect(forwarded.previous_response_id).toBe("resp_123")
})
it("still injects memories as instructions when the caller sent none", async () => {
stubMemoryFetch(["Fresh profile fact"])
const { originalCreate, wrapped } = wrapResponsesClient()
await wrapped.responses.create({
model: "gpt-4o-mini",
input: "What do you remember?",
})
const forwarded = originalCreate.mock.calls[0]?.[0]
expect(String(forwarded.instructions)).toContain("Fresh profile fact")
})
it("keeps caller instructions and strips stale context when there is nothing to inject", async () => {
stubMemoryFetch([])
const { originalCreate, wrapped } = wrapResponsesClient()
await wrapped.responses.create({
model: "gpt-4o-mini",
input: "What do you remember?",
instructions: [
"Be helpful.",
'<supermemory context="user-memories" readonly>',
"Stale profile fact",
"</supermemory>",
].join("\n"),
})
const forwarded = originalCreate.mock.calls[0]?.[0]
expect(String(forwarded.instructions)).toBe("Be helpful.")
})
})