diff --git a/open-sse/executors/forceResponsesUpstream.ts b/open-sse/executors/forceResponsesUpstream.ts index 0c545d980e..4de8961a3b 100644 --- a/open-sse/executors/forceResponsesUpstream.ts +++ b/open-sse/executors/forceResponsesUpstream.ts @@ -28,6 +28,17 @@ export function shouldForceResponsesUpstream( const providerSpecificData = credentials?.providerSpecificData ?? null; if (providerSpecificData?._omnirouteForceResponsesUpstream === true) return true; if (getOpenAICompatibleType(provider, providerSpecificData) === "responses") return false; + // apiType="chat" means the operator explicitly chose the chat/completions + // wire. Don't second-guess that choice by forcing /responses just because the + // body carries namespace tools — the standard namespace→flatten path + // (openai-responses.ts) handles those correctly for chat backends. + if ( + providerSpecificData && + typeof providerSpecificData.apiType === "string" && + providerSpecificData.apiType === "chat" + ) { + return false; + } const hasResponsesShape = body.input !== undefined || diff --git a/tests/unit/executor-default-base.test.ts b/tests/unit/executor-default-base.test.ts index f144d7f224..0e21678054 100644 --- a/tests/unit/executor-default-base.test.ts +++ b/tests/unit/executor-default-base.test.ts @@ -9,6 +9,7 @@ import { mergeUpstreamExtraHeaders, setUserAgentHeader, } from "../../open-sse/executors/base.ts"; +import { shouldForceResponsesUpstream } from "../../open-sse/executors/forceResponsesUpstream.ts"; import { DefaultExecutor } from "../../open-sse/executors/default.ts"; import { PROVIDERS } from "../../open-sse/config/constants.ts"; import { @@ -1578,3 +1579,53 @@ test("DefaultExecutor.execute does not produce duplicate anthropic-version heade /^x-anthropic-billing-header: cc_version=2\.1\.220\.1f2; cc_entrypoint=cli; cch=[0-9a-f]{5};$/ ); }); + +test('shouldForceResponsesUpstream respects explicit apiType="chat" even when namespace tools are present', () => { + const body = { + input: "hi", + tools: [ + { + type: "namespace", + name: "collaboration", + tools: [ + { + name: "spawn_agent", + description: "Spawn an agent", + parameters: { type: "object", properties: { task: { type: "string" } } }, + }, + ], + }, + ], + }; + const credentials = { + providerSpecificData: { + baseUrl: "https://ark.cn-beijing.volces.com/api/coding/v3", + apiType: "chat", + }, + }; + assert.equal( + shouldForceResponsesUpstream("openai-compatible-responses-demo", body, credentials), + false + ); +}); + +test("shouldForceResponsesUpstream still forces /responses for untyped OpenAI-compatible providers with namespace tools", () => { + const body = { + input: "hi", + tools: [ + { + type: "namespace", + name: "collaboration", + tools: [ + { name: "spawn_agent", description: "Spawn an agent", parameters: { type: "object" } }, + ], + }, + ], + }; + const credentials = { + providerSpecificData: { + baseUrl: "https://proxy.example/v1", + }, + }; + assert.equal(shouldForceResponsesUpstream("openai-compatible-test", body, credentials), true); +});