fix(combo): name output_tokens as the exclusion reason instead of structured output (#12374)

When every combo target is excluded because the request's max_tokens exceeds each target's known output limit, the terminal 400 now says so — requested max_tokens against the pool's highest known ceiling — instead of the unrelated "supports structured output for this request". Diagnostics (unmet, excluded[].reason, terminalReason) are unchanged; only the message for the output_tokens primary reason moves.

Validated in a combined worktree with all 25 PRs of this batch boarded together: typecheck:core clean, 443/443 node-runner tests plus 14/14 vitest across every test file the batch touches, and check-changelog-integrity, check:cycles (418 files), check:provider-consistency (272 REGISTRY entries, 355 canonical providers), check:docs-counts, check:docs-sync (42 locales) and check-file-size all green.

Thanks @pacocartones.
This commit is contained in:
Paco Cartones
2026-09-02 08:12:22 +02:00
committed by GitHub
parent 8e474914ea
commit e7b1448281
3 changed files with 52 additions and 0 deletions

View File

@@ -0,0 +1 @@
- **fix(combo):** capability-filter exhaustion caused by `max_tokens` above every target's known output limit now reports that reason (requested `max_tokens` vs the pool's highest known ceiling) instead of the unrelated "supports structured output" message ([#12229](https://github.com/diegosouzapw/OmniRoute/issues/12229)) — thanks @DW-MediaLab

View File

@@ -627,6 +627,18 @@ export type CompatFilterOptions = {
failOpen?: boolean;
};
function highestKnownOutputLimit(targets: ResolvedComboTarget[]): number {
let ceiling = 0;
for (const target of targets) {
const limit = getResolvedModelCapabilities({
provider: target.providerId || target.provider || null,
model: target.modelStr,
}).maxOutputTokens;
if (typeof limit === "number" && limit > ceiling) ceiling = limit;
}
return ceiling;
}
export function hasHardCapabilityFailure(reasons: string[]): boolean {
return reasons.some((reason) => HARD_COMPAT_REASONS.has(reason));
}
@@ -668,6 +680,16 @@ export function describeCapabilityFilterExhaustion(
message = `No target in combo ${name} supports tool calling; request carried ${toolCount} tools`;
} else if (primary === "vision") {
message = `No target in combo ${name} has confirmed vision support for this image request`;
} else if (primary === "output_tokens") {
// #12229: name the real reason. Collapsing this into the structured-output
// message sent operators chasing response_format when the request's
// max_tokens simply exceeded every target's known output ceiling.
const ceiling = highestKnownOutputLimit(
rejected.filter((entry) => entry.reasons.includes("output_tokens")).map((e) => e.target)
);
message =
`No target in combo ${name} can produce the requested max_tokens=${requirements.requestedOutputTokens}; ` +
`the highest known output limit in the pool is ${ceiling}`;
} else {
message = `No target in combo ${name} supports structured output for this request`;
}

View File

@@ -320,3 +320,32 @@ test("auto context estimate still dispatches when all known limits look too smal
assert.equal(result.status, 200);
assert.deepEqual(dispatches, ["openai/tiny"]);
});
test("#12229 exhaustion: output_tokens exclusion names max_tokens vs the model ceiling", () => {
saveModelsDevCapabilities({
claude: {
"claude-haiku-4-5-20251001": capabilityEntry(200000, {
tool_call: true,
structured_output: true,
limit_output: 64000,
}),
},
});
const targets = [target("claude", "claude/claude-haiku-4-5-20251001")];
const body = {
messages: [{ role: "user", content: "hoi wie ben je?" }],
max_tokens: 100000,
};
const exhaustion = describeCapabilityFilterExhaustion(targets, body, "hermes-main");
assert.ok(exhaustion);
assert.deepEqual(exhaustion!.unmet, ["output_tokens"]);
assert.equal(exhaustion!.excluded[0].reason, "output_tokens");
assert.equal(
exhaustion!.message,
"No target in combo hermes-main can produce the requested max_tokens=100000; the highest known output limit in the pool is 64000"
);
assert.doesNotMatch(exhaustion!.message, /structured output/i);
assert.equal(exhaustion!.terminalReason, "capability_mismatch");
});