From bc91fb9e54dfdb082742acc80b829203e84f8d6b Mon Sep 17 00:00:00 2001 From: Randi <55005611+rdself@users.noreply.github.com> Date: Mon, 27 Apr 2026 06:12:25 -0400 Subject: [PATCH] fix: avoid OpenAI stream options for Anthropic-compatible providers (#1654) Integrated into release/v3.7.2 --- open-sse/executors/default.ts | 33 ++++++++++---- tests/unit/cc-compatible-provider.test.ts | 1 + tests/unit/executor-default-base.test.ts | 53 ++++++++++++++++++++++- 3 files changed, 76 insertions(+), 11 deletions(-) diff --git a/open-sse/executors/default.ts b/open-sse/executors/default.ts index f587e11582..89859b8666 100644 --- a/open-sse/executors/default.ts +++ b/open-sse/executors/default.ts @@ -9,7 +9,11 @@ import { } from "../services/claudeCodeCompatible.ts"; import { getGigachatAccessToken } from "../services/gigachatAuth.ts"; import { applyProviderRequestDefaults } from "../services/providerRequestDefaults.ts"; -import { getOpenAICompatibleType, isClaudeCodeCompatible } from "../services/provider.ts"; +import { + getOpenAICompatibleType, + getTargetFormat, + isClaudeCodeCompatible, +} from "../services/provider.ts"; import { sanitizeQwenThinkingToolChoice } from "../services/qwenThinking.ts"; import { buildDataRobotChatUrl } from "../config/datarobot.ts"; import { buildAzureAiChatUrl } from "../config/azureAi.ts"; @@ -343,19 +347,30 @@ export class DefaultExecutor extends BaseExecutor { */ transformRequest(model, body, stream, credentials) { void model; - void credentials; - const withDefaults = applyProviderRequestDefaults(body, this.config.requestDefaults); + let withDefaults = applyProviderRequestDefaults(body, this.config.requestDefaults); - if (stream && this.config.format === "openai") { - if (typeof withDefaults === "object" && withDefaults !== null) { - withDefaults.stream_options = { - ...(withDefaults.stream_options || {}), - include_usage: true, + if (typeof withDefaults === "object" && withDefaults !== null && !Array.isArray(withDefaults)) { + if (this.provider?.startsWith?.("anthropic-compatible-")) { + if (Object.prototype.hasOwnProperty.call(withDefaults, "stream_options")) { + const { stream_options, ...withoutStreamOptions } = withDefaults; + void stream_options; + withDefaults = withoutStreamOptions; + } + } else if ( + stream && + getTargetFormat(this.provider, credentials?.providerSpecificData) === "openai" + ) { + withDefaults = { + ...withDefaults, + stream_options: { + ...(withDefaults.stream_options || {}), + include_usage: true, + }, }; } } - if (this.provider === "qwen" && typeof body === "object" && body !== null) { + if (this.provider === "qwen" && typeof withDefaults === "object" && withDefaults !== null) { return sanitizeQwenThinkingToolChoice(withDefaults, "QwenExecutor"); } return withDefaults; diff --git a/tests/unit/cc-compatible-provider.test.ts b/tests/unit/cc-compatible-provider.test.ts index af9b47d59a..94378df356 100644 --- a/tests/unit/cc-compatible-provider.test.ts +++ b/tests/unit/cc-compatible-provider.test.ts @@ -557,6 +557,7 @@ test("handleChatCore forces SSE upstream for CC compatible providers while retur assert.equal(calls.length, 1); assert.equal(calls[0].headers.Accept, "application/json"); assert.equal(calls[0].body.stream, true); + assert.equal(calls[0].body.stream_options, undefined); assert.equal(JSON.stringify(calls[0].body).includes('"cache_control"'), false); const payload = (await result.response.json()) as any; diff --git a/tests/unit/executor-default-base.test.ts b/tests/unit/executor-default-base.test.ts index a0f630cb93..e856528f7f 100644 --- a/tests/unit/executor-default-base.test.ts +++ b/tests/unit/executor-default-base.test.ts @@ -582,13 +582,62 @@ test("DefaultExecutor.execute only injects adaptive thinking defaults for Claude assert.equal((requestBodies[1] as any).output_config, undefined); }); -test("DefaultExecutor.transformRequest is a passthrough and preserves model ids with slashes", () => { +test("DefaultExecutor.transformRequest injects OpenAI stream usage and preserves model ids with slashes", () => { const executor = new DefaultExecutor("openai"); const body = { model: "zai-org/GLM-5-FP8", messages: [{ role: "user", content: "hi" }] }; const result = executor.transformRequest("zai-org/GLM-5-FP8", body, true, {}); - assert.equal(result, body); + assert.notEqual(result, body); assert.equal(result.model, "zai-org/GLM-5-FP8"); + assert.deepEqual((result as any).stream_options, { include_usage: true }); + assert.equal((body as any).stream_options, undefined); +}); + +test("DefaultExecutor.transformRequest only injects stream usage for OpenAI chat targets", () => { + const openAICompat = new DefaultExecutor("openai-compatible-test"); + const openAIResponsesCompat = new DefaultExecutor("openai-compatible-responses-test"); + + const chatBody = { model: "gpt-4.1", messages: [{ role: "user", content: "hi" }] }; + const responsesBody = { model: "gpt-4.1", input: "hi" }; + + const chatResult = openAICompat.transformRequest("gpt-4.1", chatBody, true, { + providerSpecificData: { baseUrl: "https://proxy.example/v1" }, + }); + const responsesResult = openAIResponsesCompat.transformRequest("gpt-4.1", responsesBody, true, { + providerSpecificData: { baseUrl: "https://proxy.example/v1" }, + }); + + assert.deepEqual((chatResult as any).stream_options, { include_usage: true }); + assert.equal((responsesResult as any).stream_options, undefined); +}); + +test("DefaultExecutor.transformRequest strips stream_options from Anthropic-compatible targets", () => { + const anthropicCompat = new DefaultExecutor("anthropic-compatible-test"); + const anthropicCcCompat = new DefaultExecutor("anthropic-compatible-cc-test"); + + const anthropicBody = { + model: "claude-sonnet-4-6", + messages: [{ role: "user", content: "hi" }], + max_tokens: 1, + stream_options: { include_usage: true }, + }; + const ccBody = { + model: "claude-sonnet-4-6", + messages: [{ role: "user", content: "hi" }], + max_tokens: 1, + }; + + const anthropicResult = anthropicCompat.transformRequest( + "claude-sonnet-4-6", + anthropicBody, + true, + {} + ); + const ccResult = anthropicCcCompat.transformRequest("claude-sonnet-4-6", ccBody, true, {}); + + assert.notEqual(anthropicResult, anthropicBody); + assert.equal((anthropicResult as any).stream_options, undefined); + assert.equal((ccResult as any).stream_options, undefined); }); test("DefaultExecutor.transformRequest neutralizes incompatible tool_choice for Qwen thinking", () => {