From d792ceed1918e7ec8d595322e894b74ba90e057c Mon Sep 17 00:00:00 2001 From: SOSANA Date: Sat, 15 Aug 2026 02:29:13 -0400 Subject: [PATCH 1/4] fix(server): default store:false on /v1/responses inbound when omitted Generic Responses-API clients (AI-SDK apps such as ZCode) omit store, but the Codex backend rejects a native request without an explicit store:false ("Store must be set to false"). The chat-completions inbound already defaults store:false when absent (src/server/chat-completions.ts); apply the same default to the /v1/responses inbound so generic Responses clients work against native openai routes. Explicit values are never overridden. Adjacent context: #882 covered the outbound forward path. --- src/server/responses/core.ts | 7 ++ tests/responses-inbound-store-default.test.ts | 85 +++++++++++++++++++ 2 files changed, 92 insertions(+) create mode 100644 tests/responses-inbound-store-default.test.ts diff --git a/src/server/responses/core.ts b/src/server/responses/core.ts index c6ce07c05a..b1426b91ba 100644 --- a/src/server/responses/core.ts +++ b/src/server/responses/core.ts @@ -1579,6 +1579,13 @@ async function handleResponsesInner( } return decodeRequestErrorResponse(err, "responses"); } + // Generic Responses clients (e.g. AI-SDK apps) omit `store`, but the Codex + // backend rejects a native request without an explicit store:false. Default it + // the same way the chat-completions inbound does; never override an explicit value. + if (body && typeof body === "object" && !Array.isArray(body)) { + const rawBody = body as Record; + if (rawBody.store === undefined) rawBody.store = false; + } const comboId = !options.comboAttempt ? comboIdFromRawBody(body, config) : null; if (comboId && Object.hasOwn(config.combos ?? {}, comboId)) { options.onRequestBodyRead?.(); diff --git a/tests/responses-inbound-store-default.test.ts b/tests/responses-inbound-store-default.test.ts new file mode 100644 index 0000000000..de158e1e9c --- /dev/null +++ b/tests/responses-inbound-store-default.test.ts @@ -0,0 +1,85 @@ +/** + * Generic Responses-API clients (AI-SDK apps such as ZCode) omit `store`, but the + * Codex backend rejects a native request without an explicit store:false + * ("Store must be set to false"). The chat-completions inbound already defaults + * store:false when absent (src/server/chat-completions.ts); these tests pin the + * same default on the /v1/responses inbound and prove explicit values survive. + * + * End-to-end cases assert the captured upstream request body — the externally + * observable payload. Pattern mirrors tests/github-copilot-wire-defaults.test.ts. + */ +import { afterEach, describe, expect, test } from "bun:test"; +import { providerConfigSeed } from "../src/providers/derive"; +import { getProviderRegistryEntry } from "../src/providers/registry"; +import { handleResponses } from "../src/server/responses/core"; +import type { OcxConfig, OcxProviderConfig } from "../src/types"; + +function copilotProvider(): OcxProviderConfig { + // The entry's allowKeyAuthOverride lets tests use key auth instead of live OAuth. + return { ...providerConfigSeed(getProviderRegistryEntry("github-copilot")!), authMode: "key", apiKey: "sk-test" }; +} + +describe("/v1/responses defaults store:false when the client omits it", () => { + const originalFetch = globalThis.fetch; + afterEach(() => { globalThis.fetch = originalFetch; }); + + function captureUpstream(): { urls: string[]; bodies: Promise[] } { + const urls: string[] = []; + const bodies: Promise[] = []; + globalThis.fetch = (async (input: RequestInfo | URL, init?: RequestInit) => { + urls.push(String(input)); + const body = + input instanceof Request ? input.clone().text() + : typeof init?.body === "string" ? Promise.resolve(init.body) + : Promise.resolve(""); + bodies.push(body); + return new Response("data: [DONE]\n\n", { + status: 200, + headers: { "content-type": "text/event-stream" }, + }); + }) as typeof fetch; + return { urls, bodies }; + } + + async function drive(requestStore: unknown): Promise<{ url: string; body: Record | null }> { + const { urls, bodies } = captureUpstream(); + const config = { providers: { "github-copilot": copilotProvider() } } as unknown as OcxConfig; + await handleResponses( + new Request("http://localhost/v1/responses", { + method: "POST", + headers: { "content-type": "application/json" }, + body: JSON.stringify({ + model: "github-copilot/gpt-5.4", + input: [{ role: "user", content: [{ type: "input_text", text: "ping" }] }], + stream: true, + ...(requestStore === undefined ? {} : { store: requestStore }), + }), + }), + config, + { model: "", provider: "" }, + ); + const raw = bodies[0] ? await bodies[0] : ""; + let parsed: Record | null = null; + try { parsed = raw ? (JSON.parse(raw) as Record) : null; } catch { parsed = null; } + return { url: urls[0] ?? "", body: parsed }; + } + + test("omitted store reaches the upstream Responses request as false", async () => { + const { url, body } = await drive(undefined); + expect(url).toContain("/responses"); + expect(body).not.toBeNull(); + expect((body as Record).store).toBe(false); + }); + + test("explicit store:true is preserved, never overridden", async () => { + const { body } = await drive(true); + expect(body).not.toBeNull(); + expect((body as Record).store).toBe(true); + }); + + test("explicit store:false is preserved unchanged", async () => { + const { body } = await drive(false); + expect(body).not.toBeNull(); + expect((body as Record).store).toBe(false); + }); +}); From 9ba153f01252662d64df160bee1ed5cae9deaea2 Mon Sep 17 00:00:00 2001 From: sosana Date: Sat, 15 Aug 2026 09:57:39 -0400 Subject: [PATCH 2/4] fix(server): scope store:false default to the canonical forward Codex backend The unconditional /v1/responses inbound default also hit stateful key-auth Responses upstreams (official OpenAI API, gateways), where an omitted store intentionally enables server-side storage for later previous_response_id continuation. Apply the default only after routing settles and only for authMode "forward" + openai-responses providers, mirroring the provider-level forward predicate in usesCodexForwardPoolAuth. Explicit store values are never overridden. Adds a key-auth negative regression test alongside the forward positive cases. --- src/server/responses/core.ts | 20 +++-- tests/responses-inbound-store-default.test.ts | 80 ++++++++++++------- 2 files changed, 63 insertions(+), 37 deletions(-) diff --git a/src/server/responses/core.ts b/src/server/responses/core.ts index b1426b91ba..df51645b2f 100644 --- a/src/server/responses/core.ts +++ b/src/server/responses/core.ts @@ -1080,6 +1080,19 @@ async function applyFinalRouteRequestNormalization(args: { } } + // Generic Responses clients (e.g. AI-SDK apps) omit `store`, but the canonical + // forward Codex backend rejects a native request without an explicit store:false. + // Default it only there — stateful key-auth Responses upstreams intentionally keep + // the omitted-store server-side default for previous_response_id reuse — and never + // override an explicit value. + if ( + route.provider.adapter === "openai-responses" && route.provider.authMode === "forward" + && parsed._rawBody && typeof parsed._rawBody === "object" + && (parsed._rawBody as Record).store === undefined + ) { + (parsed._rawBody as Record).store = false; + } + // Final selected model before virtual wire-model rewriting (Pro aliases). const finalSelectedModelId = route.modelId; @@ -1579,13 +1592,6 @@ async function handleResponsesInner( } return decodeRequestErrorResponse(err, "responses"); } - // Generic Responses clients (e.g. AI-SDK apps) omit `store`, but the Codex - // backend rejects a native request without an explicit store:false. Default it - // the same way the chat-completions inbound does; never override an explicit value. - if (body && typeof body === "object" && !Array.isArray(body)) { - const rawBody = body as Record; - if (rawBody.store === undefined) rawBody.store = false; - } const comboId = !options.comboAttempt ? comboIdFromRawBody(body, config) : null; if (comboId && Object.hasOwn(config.combos ?? {}, comboId)) { options.onRequestBodyRead?.(); diff --git a/tests/responses-inbound-store-default.test.ts b/tests/responses-inbound-store-default.test.ts index de158e1e9c..c52f61feb4 100644 --- a/tests/responses-inbound-store-default.test.ts +++ b/tests/responses-inbound-store-default.test.ts @@ -1,37 +1,50 @@ /** * Generic Responses-API clients (AI-SDK apps such as ZCode) omit `store`, but the - * Codex backend rejects a native request without an explicit store:false - * ("Store must be set to false"). The chat-completions inbound already defaults - * store:false when absent (src/server/chat-completions.ts); these tests pin the - * same default on the /v1/responses inbound and prove explicit values survive. + * canonical forward Codex backend rejects a native request without an explicit + * store:false ("Store must be set to false"). The default is applied only after + * routing settles and only for authMode "forward" + openai-responses providers: + * stateful key-auth Responses upstreams (the official OpenAI API, gateways) + * intentionally keep the omitted-store server-side default so a later turn can + * continue through an unexpanded previous_response_id. These tests pin the scoped + * default (forward positive cases), the key-auth negative, and explicit-value + * survival on both sides. * * End-to-end cases assert the captured upstream request body — the externally - * observable payload. Pattern mirrors tests/github-copilot-wire-defaults.test.ts. + * observable payload. Pattern mirrors tests/responses-compaction-routing.test.ts + * and tests/github-copilot-wire-defaults.test.ts. */ import { afterEach, describe, expect, test } from "bun:test"; -import { providerConfigSeed } from "../src/providers/derive"; -import { getProviderRegistryEntry } from "../src/providers/registry"; -import { handleResponses } from "../src/server/responses/core"; +import { handleResponses } from "../src/server/responses"; import type { OcxConfig, OcxProviderConfig } from "../src/types"; -function copilotProvider(): OcxProviderConfig { - // The entry's allowKeyAuthOverride lets tests use key auth instead of live OAuth. - return { ...providerConfigSeed(getProviderRegistryEntry("github-copilot")!), authMode: "key", apiKey: "sk-test" }; +function providerConfig(overrides: Partial = {}): OcxConfig { + return { + defaultProvider: "gw", + providers: { + gw: { + adapter: "openai-responses", + baseUrl: "https://gateway.example/v1", + authMode: "key", + apiKey: "test-key", + ...overrides, + }, + }, + } as unknown as OcxConfig; } -describe("/v1/responses defaults store:false when the client omits it", () => { +describe("/v1/responses defaults store:false only for the canonical forward Codex backend", () => { const originalFetch = globalThis.fetch; afterEach(() => { globalThis.fetch = originalFetch; }); - function captureUpstream(): { urls: string[]; bodies: Promise[] } { + function captureUpstream(): { urls: string[]; bodies: string[] } { const urls: string[] = []; - const bodies: Promise[] = []; + const bodies: string[] = []; globalThis.fetch = (async (input: RequestInfo | URL, init?: RequestInit) => { urls.push(String(input)); const body = - input instanceof Request ? input.clone().text() - : typeof init?.body === "string" ? Promise.resolve(init.body) - : Promise.resolve(""); + input instanceof Request ? await input.clone().text() + : typeof init?.body === "string" ? init.body + : ""; bodies.push(body); return new Response("data: [DONE]\n\n", { status: 200, @@ -41,44 +54,51 @@ describe("/v1/responses defaults store:false when the client omits it", () => { return { urls, bodies }; } - async function drive(requestStore: unknown): Promise<{ url: string; body: Record | null }> { + async function drive( + config: OcxConfig, + store: unknown, + ): Promise<{ url: string; body: Record | null }> { const { urls, bodies } = captureUpstream(); - const config = { providers: { "github-copilot": copilotProvider() } } as unknown as OcxConfig; await handleResponses( new Request("http://localhost/v1/responses", { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify({ - model: "github-copilot/gpt-5.4", - input: [{ role: "user", content: [{ type: "input_text", text: "ping" }] }], + model: "gw/some-model", + input: [{ type: "message", role: "user", content: [{ type: "input_text", text: "ping" }] }], stream: true, - ...(requestStore === undefined ? {} : { store: requestStore }), + ...(store === undefined ? {} : { store }), }), }), config, { model: "", provider: "" }, ); - const raw = bodies[0] ? await bodies[0] : ""; let parsed: Record | null = null; - try { parsed = raw ? (JSON.parse(raw) as Record) : null; } catch { parsed = null; } + try { parsed = bodies[0] ? (JSON.parse(bodies[0]) as Record) : null; } catch { parsed = null; } return { url: urls[0] ?? "", body: parsed }; } - test("omitted store reaches the upstream Responses request as false", async () => { - const { url, body } = await drive(undefined); + test("forward route: omitted store reaches the upstream Responses request as false", async () => { + const { url, body } = await drive(providerConfig({ authMode: "forward" }), undefined); expect(url).toContain("/responses"); expect(body).not.toBeNull(); expect((body as Record).store).toBe(false); }); - test("explicit store:true is preserved, never overridden", async () => { - const { body } = await drive(true); + test("key-auth route: omitted store is NOT injected (stateful upstream keeps its default)", async () => { + const { body } = await drive(providerConfig(), undefined); + expect(body).not.toBeNull(); + expect(!("store" in (body as Record))).toBe(true); + }); + + test("forward route: explicit store:true is preserved", async () => { + const { body } = await drive(providerConfig({ authMode: "forward" }), true); expect(body).not.toBeNull(); expect((body as Record).store).toBe(true); }); - test("explicit store:false is preserved unchanged", async () => { - const { body } = await drive(false); + test("key-auth route: explicit store:false is preserved", async () => { + const { body } = await drive(providerConfig(), false); expect(body).not.toBeNull(); expect((body as Record).store).toBe(false); }); From 621b14d67df26ee45644d486feb725ac103104e4 Mon Sep 17 00:00:00 2001 From: sosana Date: Sat, 15 Aug 2026 11:38:53 -0400 Subject: [PATCH 3/4] test(responses): pin forward-route explicit store:false preservation Completes the auth-mode x explicit-value matrix in the store-default regression suite: forward-auth routes must keep an explicit store:false untouched, not just an explicit store:true. --- tests/responses-inbound-store-default.test.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/tests/responses-inbound-store-default.test.ts b/tests/responses-inbound-store-default.test.ts index c52f61feb4..75aeccf1b6 100644 --- a/tests/responses-inbound-store-default.test.ts +++ b/tests/responses-inbound-store-default.test.ts @@ -97,6 +97,12 @@ describe("/v1/responses defaults store:false only for the canonical forward Code expect((body as Record).store).toBe(true); }); + test("forward route: explicit store:false is preserved", async () => { + const { body } = await drive(providerConfig({ authMode: "forward" }), false); + expect(body).not.toBeNull(); + expect((body as Record).store).toBe(false); + }); + test("key-auth route: explicit store:false is preserved", async () => { const { body } = await drive(providerConfig(), false); expect(body).not.toBeNull(); From 068ff1e67aaf2c7ea73251a498633ff04fa63250 Mon Sep 17 00:00:00 2001 From: sosana Date: Sat, 15 Aug 2026 14:38:10 -0400 Subject: [PATCH 4/4] fix(server): gate store:false default on the canonical Codex forward base URL Per review: reuse isCanonicalOpenAiForwardProvider() so custom Responses- compatible forward gateways keep the omitted-store server-side default instead of receiving an injected store:false. Adds the requested negative regression (non-canonical forward gateway + omitted store stays omitted) and points the forward fixtures at the canonical Codex base URL. --- src/server/responses/core.ts | 8 +++--- tests/responses-inbound-store-default.test.ts | 25 +++++++++++++------ 2 files changed, 22 insertions(+), 11 deletions(-) diff --git a/src/server/responses/core.ts b/src/server/responses/core.ts index df51645b2f..760e9c0d8a 100644 --- a/src/server/responses/core.ts +++ b/src/server/responses/core.ts @@ -1082,11 +1082,11 @@ async function applyFinalRouteRequestNormalization(args: { // Generic Responses clients (e.g. AI-SDK apps) omit `store`, but the canonical // forward Codex backend rejects a native request without an explicit store:false. - // Default it only there — stateful key-auth Responses upstreams intentionally keep - // the omitted-store server-side default for previous_response_id reuse — and never - // override an explicit value. + // Default it only there — every other Responses upstream (key-auth providers and + // custom forward gateways) intentionally keeps the omitted-store server-side + // default for previous_response_id reuse — and never override an explicit value. if ( - route.provider.adapter === "openai-responses" && route.provider.authMode === "forward" + isCanonicalOpenAiForwardProvider(route.provider) && parsed._rawBody && typeof parsed._rawBody === "object" && (parsed._rawBody as Record).store === undefined ) { diff --git a/tests/responses-inbound-store-default.test.ts b/tests/responses-inbound-store-default.test.ts index 75aeccf1b6..85038187d1 100644 --- a/tests/responses-inbound-store-default.test.ts +++ b/tests/responses-inbound-store-default.test.ts @@ -2,18 +2,20 @@ * Generic Responses-API clients (AI-SDK apps such as ZCode) omit `store`, but the * canonical forward Codex backend rejects a native request without an explicit * store:false ("Store must be set to false"). The default is applied only after - * routing settles and only for authMode "forward" + openai-responses providers: - * stateful key-auth Responses upstreams (the official OpenAI API, gateways) - * intentionally keep the omitted-store server-side default so a later turn can - * continue through an unexpanded previous_response_id. These tests pin the scoped - * default (forward positive cases), the key-auth negative, and explicit-value - * survival on both sides. + * routing settles and only on the canonical Codex forward backend + * (isCanonicalOpenAiForwardProvider: adapter + forward auth + the canonical base + * URL). Every other Responses upstream — key-auth providers and custom forward + * gateways alike — intentionally keeps the omitted-store server-side default so a + * later turn can continue through an unexpanded previous_response_id. These tests + * pin the scoped default (forward positive cases), the key-auth and custom-gateway + * negatives, and explicit-value survival on both sides. * * End-to-end cases assert the captured upstream request body — the externally * observable payload. Pattern mirrors tests/responses-compaction-routing.test.ts * and tests/github-copilot-wire-defaults.test.ts. */ import { afterEach, describe, expect, test } from "bun:test"; +import { CODEX_FORWARD_BASE_URL } from "../src/providers/openai-tiers"; import { handleResponses } from "../src/server/responses"; import type { OcxConfig, OcxProviderConfig } from "../src/types"; @@ -23,7 +25,7 @@ function providerConfig(overrides: Partial = {}): OcxConfig { providers: { gw: { adapter: "openai-responses", - baseUrl: "https://gateway.example/v1", + baseUrl: CODEX_FORWARD_BASE_URL, authMode: "key", apiKey: "test-key", ...overrides, @@ -91,6 +93,15 @@ describe("/v1/responses defaults store:false only for the canonical forward Code expect(!("store" in (body as Record))).toBe(true); }); + test("custom forward gateway: omitted store is NOT injected (non-canonical base URL keeps its default)", async () => { + const { body } = await drive( + providerConfig({ authMode: "forward", baseUrl: "https://gateway.example/v1" }), + undefined, + ); + expect(body).not.toBeNull(); + expect(!("store" in (body as Record))).toBe(true); + }); + test("forward route: explicit store:true is preserved", async () => { const { body } = await drive(providerConfig({ authMode: "forward" }), true); expect(body).not.toBeNull();