feat(responses): degrade background mode to synchronous execution (#2164)

Integrated into release/v3.8.0 — degrades background:true to synchronous execution instead of 400, enabling Capy and similar clients that set background:true by default to work seamlessly.
This commit is contained in:
Anton
2026-05-12 02:03:26 +02:00
committed by GitHub
parent 25199b20fe
commit e5974c4ae3
2 changed files with 91 additions and 19 deletions

View File

@@ -77,13 +77,28 @@ export function openaiResponsesToOpenAIRequest(
}
}
if (root.background) {
throw unsupportedFeature(
"Unsupported Responses API feature: background mode is not supported by omniroute"
const result: JsonRecord = { ...root };
// background: true requests a deferred Responses API run (the upstream
// returns 202 with response_id and the client polls GET /responses/<id>).
// OmniRoute is a forward proxy that streams responses synchronously —
// implementing the queue/poll contract would require persistence and a
// separate retrieval surface. Degrade: log a marker when true was
// actually requested (operators can observe clients that should be
// reconfigured) and strip the flag. Clients that set background=true
// opportunistically (Capy Captain Pro, Codex agents) work unchanged.
// Clients that strictly require the async contract still observe a
// completed response on the first poll and can adapt.
if (result.background === true) {
const providerStr = toString(credentialRecord.provider);
const modelStr = toString(model);
console.warn(
`BACKGROUND_DEGRADE provider=${providerStr || "unknown"} model=${modelStr || "unknown"}`
);
}
const result: JsonRecord = { ...root };
if (result.background !== undefined) {
delete result.background;
}
const messages: JsonRecord[] = [];
result.messages = messages;

View File

@@ -110,7 +110,7 @@ test("Responses -> Chat filters orphan tool outputs and supports role-based mess
});
});
test("Responses -> Chat rejects unsupported built-in tools and background mode", () => {
test("Responses -> Chat rejects unsupported built-in tools", () => {
assert.throws(
() =>
openaiResponsesToOpenAIRequest(
@@ -124,20 +124,77 @@ test("Responses -> Chat rejects unsupported built-in tools and background mode",
),
(error: any) => error.statusCode === 400 && error.errorType === "unsupported_feature"
);
});
assert.throws(
() =>
openaiResponsesToOpenAIRequest(
"gpt-4o",
{
input: [],
background: true,
},
false,
null
),
(error: any) => error.statusCode === 400 && error.errorType === "unsupported_feature"
);
test("Responses -> Chat strips background flag and degrades to synchronous execution", () => {
// Previously this threw 400 unsupported_feature. OmniRoute is a forward proxy
// and cannot host the deferred run + poll contract, so background=true is
// silently dropped and the request runs synchronously. Clients that set the
// flag opportunistically (Capy Captain Pro, Codex agents) work unchanged.
const warnLog: string[] = [];
const originalWarn = console.warn;
console.warn = (msg: unknown) => warnLog.push(String(msg));
try {
const result = openaiResponsesToOpenAIRequest(
"gpt-5.5",
{
input: [{ role: "user", content: [{ type: "input_text", text: "hi" }] }],
background: true,
},
true,
{ provider: "codex" }
);
const r = result as Record<string, unknown>;
assert.equal(r.background, undefined, "background flag must be stripped from output");
assert.ok(Array.isArray(r.messages), "translation must complete and produce messages");
assert.equal((r.messages as unknown[]).length, 1, "user message must be preserved");
assert.ok(
warnLog.some((m) => m.startsWith("BACKGROUND_DEGRADE provider=codex model=gpt-5.5")),
"BACKGROUND_DEGRADE warning log must be emitted when background=true"
);
} finally {
console.warn = originalWarn;
}
});
test("Responses -> Chat passes through when background flag is unset or false (no degrade log)", () => {
// Verifies the inverse of the degradation case: when background is absent or
// explicitly false, no warning should be emitted and the request body should
// not carry a residual background field. Guards against accidental log spam
// and confirms the degradation logic is conditional on background === true.
const warnLog: string[] = [];
const originalWarn = console.warn;
console.warn = (msg: unknown) => warnLog.push(String(msg));
try {
// Case 1: background unset
const r1 = openaiResponsesToOpenAIRequest(
"gpt-5.5",
{ input: [{ role: "user", content: [{ type: "input_text", text: "hi" }] }] },
true,
{ provider: "codex" }
) as Record<string, unknown>;
assert.equal(r1.background, undefined, "background must be absent on output (unset case)");
// Case 2: background explicitly false
const r2 = openaiResponsesToOpenAIRequest(
"gpt-5.5",
{
input: [{ role: "user", content: [{ type: "input_text", text: "hi" }] }],
background: false,
},
true,
{ provider: "codex" }
) as Record<string, unknown>;
assert.equal(r2.background, undefined, "background must be stripped on output (false case)");
assert.equal(
warnLog.filter((m) => m.startsWith("BACKGROUND_DEGRADE")).length,
0,
"BACKGROUND_DEGRADE must NOT be emitted for unset or false values"
);
} finally {
console.warn = originalWarn;
}
});
test("Chat -> Responses converts messages, tool calls, tool outputs, tools and pass-through params", () => {