diff --git a/packages/ai/CHANGELOG.md b/packages/ai/CHANGELOG.md index 1de0793f..5619050e 100644 --- a/packages/ai/CHANGELOG.md +++ b/packages/ai/CHANGELOG.md @@ -8,6 +8,7 @@ ### Fixed +- Fixed `openai-responses` compatibility by adding `compat.sendSessionIdHeader: false`, allowing strict OpenAI-compatible proxies to omit the underscore-containing `session_id` header while still sending other session-affinity headers ([#3579](https://github.com/badlogic/pi-mono/issues/3579)) - Fixed `anthropic-messages` tool streaming compatibility by adding `compat.supportsEagerToolInputStreaming`, allowing Anthropic-compatible providers to omit per-tool `eager_input_streaming` and use the legacy fine-grained tool streaming beta header instead ([#3575](https://github.com/badlogic/pi-mono/issues/3575)) - Fixed `supportsXhigh()` to recognize `openai-codex` `gpt-5.5`, preserving `xhigh` reasoning requests instead of clamping them to `high`. - Fixed `openai-completions` streamed tool-call assembly to coalesce deltas by stable tool index when OpenAI-compatible gateways mutate tool call IDs mid-stream, preventing malformed Kimi K2.6/OpenCode tool streams from splitting one call into multiple bogus tool calls ([#3576](https://github.com/badlogic/pi-mono/issues/3576)) diff --git a/packages/ai/src/providers/openai-responses.ts b/packages/ai/src/providers/openai-responses.ts index b8a991b6..135f57ef 100644 --- a/packages/ai/src/providers/openai-responses.ts +++ b/packages/ai/src/providers/openai-responses.ts @@ -180,7 +180,9 @@ function createClient( } if (sessionId) { - headers.session_id = sessionId; + if (model.compat?.sendSessionIdHeader !== false) { + headers.session_id = sessionId; + } headers["x-client-request-id"] = sessionId; } diff --git a/packages/ai/src/types.ts b/packages/ai/src/types.ts index aa45ba96..8710bbd6 100644 --- a/packages/ai/src/types.ts +++ b/packages/ai/src/types.ts @@ -300,7 +300,8 @@ export interface OpenAICompletionsCompat { /** Compatibility settings for OpenAI Responses APIs. */ export interface OpenAIResponsesCompat { - // Reserved for future use + /** Whether to send the OpenAI `session_id` cache-affinity header from `options.sessionId` when caching is enabled. Default: true. */ + sendSessionIdHeader?: boolean; } /** diff --git a/packages/ai/test/openai-completions-tool-result-images.test.ts b/packages/ai/test/openai-completions-tool-result-images.test.ts index 50fce94e..4f912278 100644 --- a/packages/ai/test/openai-completions-tool-result-images.test.ts +++ b/packages/ai/test/openai-completions-tool-result-images.test.ts @@ -54,7 +54,7 @@ function buildToolResult(toolCallId: string, timestamp: number): ToolResultMessa describe("openai-completions convertMessages", () => { it("batches tool-result images after consecutive tool results", () => { - const baseModel = getModel("openai", "gpt-4o-mini"); + const { compat: _compat, ...baseModel } = getModel("openai", "gpt-4o-mini"); const model: Model<"openai-completions"> = { ...baseModel, api: "openai-completions", diff --git a/packages/ai/test/openai-responses-copilot-provider.test.ts b/packages/ai/test/openai-responses-copilot-provider.test.ts index ef55603b..cf25103c 100644 --- a/packages/ai/test/openai-responses-copilot-provider.test.ts +++ b/packages/ai/test/openai-responses-copilot-provider.test.ts @@ -108,6 +108,18 @@ describe("openai-responses provider defaults", () => { expect(captured).toEqual({ sessionId: "session-123", clientRequestId: "session-123" }); }); + it("can omit the session_id header while preserving other cache-affinity headers", async () => { + const proxyModel: Model<"openai-responses"> = { + ...getModel("openai", "gpt-5.4"), + provider: "opencode", + baseUrl: "https://proxy.example.com/v1", + compat: { sendSessionIdHeader: false }, + }; + const captured = await captureOpenAIResponseHeaders({ sessionId: "session-123" }, proxyModel); + + expect(captured).toEqual({ sessionId: null, clientRequestId: "session-123" }); + }); + it("lets explicit headers override the default OpenAI cache-affinity headers", async () => { const captured = await captureOpenAIResponseHeaders({ sessionId: "session-123",