From 6b259812a748e57b298bebbcd6930ffe120a129e Mon Sep 17 00:00:00 2001 From: "Bob.Hou" Date: Fri, 28 Aug 2026 15:25:19 -0400 Subject: [PATCH] fix(sse): preserve store parameter semantics for openai-compatible responses (#11826) (#11916) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit stripStore() now forces store=false for stateless OpenAI-compatible Responses-API targets unless the connection explicitly opts in via providerSpecificData.openaiStoreEnabled, instead of only handling the openai/agentrouter cases — a client-supplied store value previously passed through untouched to backends that don't actually persist responses server-side. Closes #11826. Thanks! --- open-sse/config/cliFingerprints.ts | 2 +- open-sse/executors/base.ts | 5 +- open-sse/handlers/chatCore.ts | 7 +- .../handlers/chatCore/agentRouterProtocol.ts | 15 ++- tests/unit/strip-store-responses.test.ts | 120 ++++++++++++++++++ 5 files changed, 145 insertions(+), 4 deletions(-) create mode 100644 tests/unit/strip-store-responses.test.ts diff --git a/open-sse/config/cliFingerprints.ts b/open-sse/config/cliFingerprints.ts index f97fa41aad..efa902e137 100644 --- a/open-sse/config/cliFingerprints.ts +++ b/open-sse/config/cliFingerprints.ts @@ -265,7 +265,7 @@ export function orderHeaders( * Apply a CLI fingerprint to headers and body. * Returns { headers, bodyString } with the correct ordering. */ -function stripInternalBodyFields(body: unknown): unknown { +export function stripInternalBodyFields(body: unknown): unknown { if (!body || typeof body !== "object" || Array.isArray(body)) return body; const record = body as Record; diff --git a/open-sse/executors/base.ts b/open-sse/executors/base.ts index 6dc63fc72f..11d5f9087c 100644 --- a/open-sse/executors/base.ts +++ b/open-sse/executors/base.ts @@ -30,7 +30,7 @@ import { addParamToBlocklist, isAutoLearnGloballyEnabled, } from "@/lib/db/paramFilters"; -import { applyFingerprint, isCliCompatEnabled } from "../config/cliFingerprints.ts"; +import { applyFingerprint, isCliCompatEnabled, stripInternalBodyFields } from "../config/cliFingerprints.ts"; import { supportsClaudeMaxEffort, supportsXHighEffort } from "../config/providerModels.ts"; import { getThinkingBudgetConfig, ThinkingMode } from "../services/thinkingBudget.ts"; import { @@ -582,6 +582,8 @@ export class BaseExecutor { if (cloned[key] === "") delete cloned[key]; } + stripInternalBodyFields(cloned); + return cloned; } @@ -1393,6 +1395,7 @@ export class BaseExecutor { ); } + stripInternalBodyFields(transformedBody); let bodyString = JSON.stringify(transformedBody); const shouldFingerprint = diff --git a/open-sse/handlers/chatCore.ts b/open-sse/handlers/chatCore.ts index 374c8e3b6f..b82bf41cb8 100644 --- a/open-sse/handlers/chatCore.ts +++ b/open-sse/handlers/chatCore.ts @@ -2808,7 +2808,12 @@ export async function handleChatCore({ log?.debug?.("PARAMS", `Renamed max_completion_tokens to max_tokens for ${model}`); } - stripStore(translatedBody, provider, targetFormat); + stripStore( + translatedBody, + provider, + targetFormat, + credentials?.providerSpecificData as Record | null | undefined + ); // Chat clients may send stream_options.include_usage, but OpenAI Responses // upstreams (including Azure AI Foundry /responses) reject stream_options. diff --git a/open-sse/handlers/chatCore/agentRouterProtocol.ts b/open-sse/handlers/chatCore/agentRouterProtocol.ts index b5cbe5dc71..61bd6568fa 100644 --- a/open-sse/handlers/chatCore/agentRouterProtocol.ts +++ b/open-sse/handlers/chatCore/agentRouterProtocol.ts @@ -26,8 +26,21 @@ export function usesClaudeBridge( export function stripStore( body: Record, provider: string, - targetFormat: string + targetFormat: string, + providerSpecificData?: unknown ): void { + if (provider.startsWith("openai-compatible-") && targetFormat === FORMATS.OPENAI_RESPONSES) { + const psd = + providerSpecificData && typeof providerSpecificData === "object" + ? (providerSpecificData as Record) + : undefined; + if (psd?.openaiStoreEnabled === true) { + return; + } + body.store = false; + return; + } + const supportsStore = provider === "openai" || (provider === "agentrouter" && targetFormat === FORMATS.OPENAI_RESPONSES); diff --git a/tests/unit/strip-store-responses.test.ts b/tests/unit/strip-store-responses.test.ts new file mode 100644 index 0000000000..6837daeb02 --- /dev/null +++ b/tests/unit/strip-store-responses.test.ts @@ -0,0 +1,120 @@ +import assert from "node:assert/strict"; +import { createServer } from "node:http"; +import test from "node:test"; + +import { DefaultExecutor } from "../../open-sse/executors/default.ts"; +import { stripStore } from "../../open-sse/handlers/chatCore/agentRouterProtocol.ts"; +import { FORMATS } from "../../open-sse/translator/formats.ts"; + +const COMPATIBLE_PROVIDER = "openai-compatible-responses-test"; + +test("stripStore forces store=false for stateless OpenAI-compatible Responses requests", () => { + for (const initialStore of [undefined, false, true]) { + const body: Record = {}; + if (initialStore !== undefined) body.store = initialStore; + + stripStore(body, COMPATIBLE_PROVIDER, FORMATS.OPENAI_RESPONSES, {}); + + assert.equal(body.store, false); + } +}); + +test("stripStore preserves client store values for opted-in OpenAI-compatible Responses requests", () => { + for (const initialStore of [false, true]) { + const body: Record = { store: initialStore }; + + stripStore(body, COMPATIBLE_PROVIDER, FORMATS.OPENAI_RESPONSES, { + openaiStoreEnabled: true, + }); + + assert.equal(body.store, initialStore); + } + + const omitted: Record = {}; + stripStore(omitted, COMPATIBLE_PROVIDER, FORMATS.OPENAI_RESPONSES, { + openaiStoreEnabled: true, + }); + assert.equal("store" in omitted, false); +}); + +test("stripStore keeps existing OpenAI and AgentRouter behavior", () => { + const cases = [ + { provider: "openai", targetFormat: FORMATS.OPENAI, expected: true }, + { provider: "openai", targetFormat: FORMATS.OPENAI_RESPONSES, expected: true }, + { provider: "agentrouter", targetFormat: FORMATS.OPENAI_RESPONSES, expected: true }, + { provider: "agentrouter", targetFormat: FORMATS.OPENAI, expected: false }, + ]; + + for (const { provider, targetFormat, expected } of cases) { + const body: Record = { store: true }; + stripStore(body, provider, targetFormat, {}); + assert.equal("store" in body, expected, `${provider}/${targetFormat}`); + } +}); + +test("stripStore removes store outside OpenAI-compatible Responses targets", () => { + const cases = [ + { provider: COMPATIBLE_PROVIDER, targetFormat: FORMATS.OPENAI }, + { provider: "anthropic", targetFormat: FORMATS.CLAUDE }, + ]; + + for (const { provider, targetFormat } of cases) { + const body: Record = { store: false }; + stripStore(body, provider, targetFormat, { openaiStoreEnabled: true }); + assert.equal("store" in body, false, `${provider}/${targetFormat}`); + } +}); + +test("DefaultExecutor never serializes native passthrough markers upstream", async () => { + let capturedBody: Record | null = null; + const server = createServer((request, response) => { + let rawBody = ""; + request.setEncoding("utf8"); + request.on("data", (chunk) => { + rawBody += chunk; + }); + request.on("end", () => { + capturedBody = JSON.parse(rawBody); + response.writeHead(200, { "Content-Type": "application/json" }); + response.end(JSON.stringify({ id: "resp_test", object: "response", output: [] })); + }); + }); + await new Promise((resolve) => server.listen(0, "127.0.0.1", resolve)); + const address = server.address(); + assert.ok(address && typeof address !== "string"); + + try { + const executor = new DefaultExecutor(COMPATIBLE_PROVIDER); + await executor.execute({ + model: "gpt-5.6-test", + body: { + model: "gpt-5.6-test", + input: "hi", + store: false, + _nativeOpenAICompatibleResponsesPassthrough: true, + _nativeCodexPassthrough: true, + _nativeXaiResponsesPassthrough: true, + _omnirouteResponsesStore: false, + }, + stream: false, + credentials: { + apiKey: "test-key", + providerSpecificData: { + apiType: "responses", + baseUrl: `http://127.0.0.1:${address.port}/v1`, + }, + }, + }); + } finally { + await new Promise((resolve, reject) => + server.close((error) => (error ? reject(error) : resolve())) + ); + } + + assert.ok(capturedBody); + assert.equal(capturedBody.store, false); + assert.equal(capturedBody._nativeOpenAICompatibleResponsesPassthrough, undefined); + assert.equal(capturedBody._nativeCodexPassthrough, undefined); + assert.equal(capturedBody._nativeXaiResponsesPassthrough, undefined); + assert.equal(capturedBody._omnirouteResponsesStore, undefined); +});