fix(sse): preserve Responses combo payloads (#8310)

This commit is contained in:
Ridho Pratama
2026-07-24 19:36:00 +07:00
committed by GitHub
parent 14f4c67598
commit ddbd054e49
3 changed files with 41 additions and 5 deletions

View File

@@ -0,0 +1 @@
- **fix(sse):** Combo middleware preserves OpenAI Responses request bodies and maps combo system overrides to `instructions`, preventing Responses-native fallbacks from receiving an unsupported `messages` parameter. ([#8310](https://github.com/diegosouzapw/OmniRoute/pull/8310)) — thanks @ridho9

View File

@@ -180,16 +180,24 @@ export function applyComboAgentMiddleware(
): { body: Record<string, unknown>; pinnedModel: string | null } {
if (!comboConfig) return { body, pinnedModel: null };
let messages: Message[] = Array.isArray(body.messages) ? [...body.messages] : [];
const hasMessages = Array.isArray(body.messages);
const isResponsesRequest =
Object.prototype.hasOwnProperty.call(body, "input") ||
Object.prototype.hasOwnProperty.call(body, "instructions");
const systemMessage =
typeof comboConfig.system_message === "string" && comboConfig.system_message.trim()
? comboConfig.system_message
: null;
let messages: Message[] = hasMessages ? [...(body.messages as Message[])] : [];
let pinnedModel: string | null = null;
// Context cache pinning is handled server-side in combo.ts via
// session_model_history. No client-side <omniModel> tag extraction needed.
pinnedModel = null;
// 2. System message override
if (comboConfig.system_message && comboConfig.system_message.trim()) {
messages = applySystemMessageOverride(messages, comboConfig.system_message);
// 2. System message override. Responses API uses top-level instructions instead of messages.
if (systemMessage && !isResponsesRequest) {
messages = applySystemMessageOverride(messages, systemMessage);
}
// 3. Tool filter
@@ -206,7 +214,8 @@ export function applyComboAgentMiddleware(
return {
body: {
...body,
messages,
...(isResponsesRequest && systemMessage ? { instructions: systemMessage } : {}),
...(hasMessages ? { messages } : {}),
...(filteredTools !== body.tools && { tools: filteredTools }),
},
pinnedModel,

View File

@@ -184,6 +184,32 @@ test("combo agent middleware covers system override, tool filtering, tag strippi
assert.equal(passthrough.body, body);
});
test("combo agent middleware preserves Responses API input and instructions", () => {
const body = {
model: "combo/default",
input: "Reply with exactly: pong",
};
const unchanged = comboAgentMiddleware.applyComboAgentMiddleware(
body,
{ context_cache_protection: true },
"openai/gpt-4o"
);
assert.deepEqual(unchanged.body, body);
assert.equal(Object.hasOwn(unchanged.body, "messages"), false);
const overridden = comboAgentMiddleware.applyComboAgentMiddleware(
{ ...body, instructions: "client instruction" },
{ system_message: "combo instruction" },
"openai/gpt-4o"
);
assert.deepEqual(overridden.body, {
...body,
instructions: "combo instruction",
});
assert.equal(Object.hasOwn(overridden.body, "messages"), false);
});
test("rate limit semaphore covers immediate acquire, timeout, cooldown drain and reset", async () => {
const release = await rateLimitSemaphore.acquire("model-a", { maxConcurrency: 1 });
assert.deepEqual(rateLimitSemaphore.getStats()["model-a"], {