feat(responses): degrade background mode to synchronous execution

OpenAI Responses API supports background: true for async/deferred runs
(returns 202 with response_id + GET /responses/<id> polling). Implementing
the full background contract requires queue/poll infrastructure that
OmniRoute does not have as a forward proxy.

Previously the translator rejected such requests with HTTP 400
"Unsupported Responses API feature: background mode is not supported by
omniroute". This breaks clients that opportunistically set background=true
for long-running tasks (Capy Captain Pro, Codex agents) even when the
underlying execution would complete in a normal HTTP timeout window.

Degrade silently: strip the background flag and run synchronously. The
client receives the full response in one round-trip with status=completed.
Clients that strictly require the async contract still observe a completed
response on the first poll and can adapt.

The existing test that asserted background=true throws is split into two:
- the unsupported-tool-type branch keeps the throw assertion
- the background branch now asserts the flag is stripped and translation
  completes with the expected messages

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
OmniRoute Ops
2026-05-11 13:23:57 +00:00
parent fea8e5a1e9
commit 970e14725b
2 changed files with 33 additions and 19 deletions

View File

@@ -77,13 +77,21 @@ 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 silently: strip the flag and run
// synchronously. The client receives the full response in one round-trip
// with status=completed. 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 !== 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,26 @@ 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 result = openaiResponsesToOpenAIRequest(
"gpt-5.5",
{
input: [{ role: "user", content: [{ type: "input_text", text: "hi" }] }],
background: true,
},
true,
null
);
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");
});
test("Chat -> Responses converts messages, tool calls, tool outputs, tools and pass-through params", () => {