diff --git a/packages/ai/src/api/openai-responses.ts b/packages/ai/src/api/openai-responses.ts index c29e6adfb..8d576a702 100644 --- a/packages/ai/src/api/openai-responses.ts +++ b/packages/ai/src/api/openai-responses.ts @@ -316,7 +316,7 @@ function createClient( headers["x-session-id"] = sessionId; } else { if (compat.sessionAffinityFormat === "openai") { - headers.session_id = sessionId; + headers["session-id"] = sessionId; } headers["x-client-request-id"] = sessionId; } diff --git a/packages/ai/src/providers/compat-schema.ts b/packages/ai/src/providers/compat-schema.ts index 792b7a1b7..88defded8 100644 --- a/packages/ai/src/providers/compat-schema.ts +++ b/packages/ai/src/providers/compat-schema.ts @@ -364,7 +364,7 @@ const OpenAIResponsesCompatProperties = { }), sessionAffinityFormat: optionalSessionAffinityFormat({ description: - "Session-affinity header format. openai sends session_id and x-client-request-id; openai-nosession sends x-client-request-id; openrouter sends x-session-id. Does not affect prompt_cache_key. Default: auto-detected.", + "Session-affinity header format. openai sends session-id and x-client-request-id; openai-nosession sends x-client-request-id; openrouter sends x-session-id. Does not affect prompt_cache_key. Default: auto-detected.", }), supportsLongCacheRetention: optionalCompatBoolean({ description: diff --git a/packages/ai/test/openai-responses-compat.test.ts b/packages/ai/test/openai-responses-compat.test.ts index ec7783313..2423da236 100644 --- a/packages/ai/test/openai-responses-compat.test.ts +++ b/packages/ai/test/openai-responses-compat.test.ts @@ -35,14 +35,17 @@ async function captureOpenAIResponseHeaders( sessionId: string | null; clientRequestId: string | null; xSessionId: string | null; + legacySessionId: string | null; }> { const captured = { sessionId: null as string | null, clientRequestId: null as string | null, xSessionId: null as string | null, + legacySessionId: null as string | null, }; vi.spyOn(globalThis, "fetch").mockImplementation(async (_input, init) => { - captured.sessionId = getHeader(init?.headers, "session_id"); + captured.sessionId = getHeader(init?.headers, "session-id"); + captured.legacySessionId = getHeader(init?.headers, "session_id"); captured.clientRequestId = getHeader(init?.headers, "x-client-request-id"); captured.xSessionId = getHeader(init?.headers, "x-session-id"); return new Response("data: [DONE]\n\n", { @@ -295,6 +298,7 @@ describe("openai-responses provider defaults", () => { const captured = await captureOpenAIResponseHeaders({ sessionId: "session-123" }); expect(captured.sessionId).toBe("session-123"); + expect(captured.legacySessionId).toBeNull(); expect(captured.clientRequestId).toBe("session-123"); }); @@ -436,7 +440,7 @@ describe("openai-responses provider defaults", () => { expect(capturedPayload?.prompt_cache_key).toBe("session-opencode"); }); - it("can omit OpenAI session_id header while preserving other affinity data", async () => { + it("can omit OpenAI session-id header while preserving other affinity data", async () => { const proxyModel: Model<"openai-responses"> = { ...getModel("openai", "gpt-5.4"), provider: "opencode", @@ -463,7 +467,7 @@ describe("openai-responses provider defaults", () => { const captured = await captureOpenAIResponseHeaders({ sessionId: "session-123", headers: { - session_id: "override-session", + "session-id": "override-session", "x-client-request-id": "override-request", }, }); diff --git a/packages/ai/test/xai-responses.test.ts b/packages/ai/test/xai-responses.test.ts index 228ebe468..ca5084866 100644 --- a/packages/ai/test/xai-responses.test.ts +++ b/packages/ai/test/xai-responses.test.ts @@ -179,7 +179,8 @@ describe("xAI Responses provider", () => { expect(captured.url).toBe("https://api.x.ai/v1/responses"); expect(captured.headers.get("authorization")).toBe("Bearer xai-test-token"); expect(captured.headers.get("user-agent")).toBe(PI_USER_AGENT); - expect(captured.headers.get("session_id")).toBe("pi-session-123"); + expect(captured.headers.get("session-id")).toBe("pi-session-123"); + expect(captured.headers.get("session_id")).toBeNull(); expect(captured.body).toMatchObject({ model: "grok-4.5", store: false, diff --git a/packages/coding-agent/docs/models-configuration.md b/packages/coding-agent/docs/models-configuration.md index 2a25bdb48..830f9bfdc 100644 --- a/packages/coding-agent/docs/models-configuration.md +++ b/packages/coding-agent/docs/models-configuration.md @@ -146,7 +146,7 @@ Also applies to `azure-openai-responses` and `openai-codex-responses`. |---|---|---|---| | `supportsDeveloperRole` | boolean | `true` | Whether the provider supports the developer role instead of system. | | `supportsMidConvoSystemMessages` | boolean | `false` | Whether the exact model accepts developer or system messages after the conversation has started. When false, later system messages are folded into the leading system message. The generated catalog enables this for verified models. | -| `sessionAffinityFormat` | `"openai" \| "openai-nosession" \| "openrouter"` | | Session-affinity header format. openai sends session_id and x-client-request-id; openai-nosession sends x-client-request-id; openrouter sends x-session-id. Does not affect prompt_cache_key. Default: auto-detected. | +| `sessionAffinityFormat` | `"openai" \| "openai-nosession" \| "openrouter"` | | Session-affinity header format. openai sends session-id and x-client-request-id; openai-nosession sends x-client-request-id; openrouter sends x-session-id. Does not affect prompt_cache_key. Default: auto-detected. | | `supportsLongCacheRetention` | boolean | `true` | Whether the provider supports long prompt cache retention. This uses prompt_cache_options.ttl: "30m" on GPT-5.6+ and prompt_cache_retention: "24h" on earlier models. | | `supportsStrictMode` | boolean | | Whether the provider supports strict JSON-schema function tools. Defaults are API-specific; generated OpenAI models enable it explicitly. | | `supportsOpenAIGrammarTools` | boolean | `false` | Whether to emit OpenAI custom tools with Lark or regex grammar formats. When false, grammar-constrained tools fall back to normal function tools. The generated catalog enables this for capable models. |