fix(executors): strip stream_options for qwen non-streaming / thinking Claude Code requests (port from 9router#663) (#4374)

Claude-Code-compatible providers force the executor-level `stream` flag on
via `upstreamStream = stream || isClaudeCodeCompatible`
(open-sse/handlers/chatCore.ts), but the outgoing body keeps the caller's
original `stream: false`. The shared `stream && targetFormat === "openai"`
branch in DefaultExecutor.transformRequest then injected
`stream_options: { include_usage: true }` onto a body that still said
`stream: false`, and qwen upstream rejected the request with
`400 "'stream_options' only set this when you set stream: true"`. The same
rejection surfaced when the body carried `thinking` / `enable_thinking`.

The qwen branch now skips the injection (and strips any client-sent
`stream_options`) when the body explicitly says `stream: false` or
requests thinking, leaving regular qwen streaming requests with the
include_usage injection intact. Other providers are unaffected.

Adds a TDD regression with 4 cases covering both opt-out paths and the
normal-streaming positive control.


Inspired-by: https://github.com/decolua/9router/pull/663

Co-authored-by: anuragg-saxenaa <anuragg.saxenaa@gmail.com>
This commit is contained in:
Diego Rodrigues de Sa e Souza
2026-06-20 11:05:24 -03:00
committed by GitHub
parent b41738197d
commit 3c3dcadd4c
3 changed files with 123 additions and 2 deletions

View File

@@ -121,6 +121,7 @@ _In development — bullets added per PR; finalized at release._
- **fix(deps): bump undici to 7.28.0 and dompurify to 3.4.11 (security)** — addresses the undici SOCKS5-TLS / cache advisories and the dompurify advisory. ([#4306](https://github.com/diegosouzapw/OmniRoute/pull/4306))
- **chore(deps): bump actions/checkout from 4 to 7** — CI checkout-action update. ([#4297](https://github.com/diegosouzapw/OmniRoute/pull/4297))
- **fix(executors): strip `stream_options` for qwen non-streaming / thinking-mode Claude Code requests** — Claude-Code-compatible providers force the executor-level `stream` flag on via `upstreamStream = stream || isClaudeCodeCompatible` (`open-sse/handlers/chatCore.ts`), but the outgoing body keeps the caller's original `stream: false`. The shared `stream && targetFormat === "openai"` branch in `DefaultExecutor.transformRequest` then injected `stream_options: { include_usage: true }` onto a body that still said `stream: false`, and qwen upstream rejected it with `400 "'stream_options' only set this when you set stream: true"`. Same rejection when the body carries `thinking` / `enable_thinking`. The qwen branch now skips the injection (and strips any client-sent `stream_options`) when the body explicitly says `stream: false` or requests thinking, leaving regular qwen streaming requests with the usage injection intact. (thanks @anuragg-saxenaa)
---

View File

@@ -571,11 +571,31 @@ export class DefaultExecutor extends BaseExecutor {
withDefaults = withoutStreamOptions;
}
} else if (stream && targetFormat === "openai" && requestFormat !== "openai-responses") {
if (!credentials?.providerSpecificData?.disableStreamOptions) {
// Port of decolua/9router#663 (closes upstream #557): Qwen rejects with
// 400 "'stream_options' only set this when you set stream: true" when the
// outgoing body carries `stream: false` (Claude Code / Claude-Code-
// compatible callers force the executor-level stream flag on via
// `upstreamStream = stream || isClaudeCodeCompatible`, but the body keeps
// the caller's original `stream: false`). Same upstream also rejects the
// injection when `thinking` / `enable_thinking` is set. Skip injection in
// those cases instead of unconditionally adding `stream_options`.
const defaultsRecord = withDefaults as Record<string, unknown>;
const qwenBlocksStreamOptions =
this.provider === "qwen" &&
(defaultsRecord.stream === false ||
Boolean(defaultsRecord.thinking) ||
Boolean(defaultsRecord.enable_thinking));
if (qwenBlocksStreamOptions) {
if (Object.prototype.hasOwnProperty.call(defaultsRecord, "stream_options")) {
const withoutStreamOptions = { ...defaultsRecord };
delete withoutStreamOptions.stream_options;
withDefaults = withoutStreamOptions;
}
} else if (!credentials?.providerSpecificData?.disableStreamOptions) {
withDefaults = {
...withDefaults,
stream_options: {
...(((withDefaults as Record<string, unknown>).stream_options as object) || {}),
...((defaultsRecord.stream_options as object) || {}),
include_usage: true,
},
};

View File

@@ -0,0 +1,100 @@
/**
* Port of upstream decolua/9router#663 (closes upstream #557).
*
* Scenario: Claude Code (or any caller) hits a Qwen model with an OpenAI body
* that carries `stream: false`. OmniRoute, however, sets the executor-level
* `stream` flag to `true` for Claude-Code-compatible providers via
* `upstreamStream = stream || isClaudeCodeCompatible`
* (`open-sse/handlers/chatCore.ts`). DefaultExecutor.transformRequest then runs
* its `if (stream && targetFormat === "openai")` branch and injects
* `stream_options: { include_usage: true }` onto a body that still carries
* `stream: false`. Qwen upstream rejects with:
* 400 "'stream_options' only set this when you set stream: true"
*
* Fix mirrors upstream: when the OUTGOING body explicitly says `stream: false`,
* do NOT inject `stream_options` regardless of the executor-level `stream` arg.
* Same defensive treatment when the body carries `thinking` /
* `enable_thinking`, since the upstream PR also exempts those.
*/
import test from "node:test";
import assert from "node:assert/strict";
import { DefaultExecutor } from "../../open-sse/executors/default.ts";
test("port#663 qwen: body.stream===false → no stream_options even when executor stream=true", () => {
const executor = new DefaultExecutor("qwen");
const body = {
model: "qwen3-coder-plus",
messages: [{ role: "user", content: "hi" }],
stream: false,
};
const result = executor.transformRequest(
"qwen3-coder-plus",
body,
/* stream */ true,
{}
) as Record<string, unknown>;
assert.equal(
result.stream_options,
undefined,
"stream_options must not be injected when body.stream === false"
);
});
test("port#663 qwen: body.thinking truthy → no stream_options injection", () => {
const executor = new DefaultExecutor("qwen");
const body = {
model: "qwen3-coder-plus",
messages: [{ role: "user", content: "hi" }],
thinking: { type: "enabled" },
};
const result = executor.transformRequest(
"qwen3-coder-plus",
body,
true,
{}
) as Record<string, unknown>;
assert.equal(
result.stream_options,
undefined,
"stream_options must not be injected when thinking mode is requested"
);
});
test("port#663 qwen: body.enable_thinking truthy → no stream_options injection", () => {
const executor = new DefaultExecutor("qwen");
const body = {
model: "qwen3-coder-plus",
messages: [{ role: "user", content: "hi" }],
enable_thinking: true,
};
const result = executor.transformRequest(
"qwen3-coder-plus",
body,
true,
{}
) as Record<string, unknown>;
assert.equal(
result.stream_options,
undefined,
"stream_options must not be injected when enable_thinking is true"
);
});
test("port#663 qwen: normal streaming request still injects stream_options.include_usage", () => {
const executor = new DefaultExecutor("qwen");
const body = {
model: "qwen3-coder-plus",
messages: [{ role: "user", content: "hi" }],
};
const result = executor.transformRequest(
"qwen3-coder-plus",
body,
true,
{}
) as Record<string, unknown>;
assert.deepEqual(
result.stream_options,
{ include_usage: true },
"regular qwen streaming requests must keep the include_usage injection"
);
});