mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-08-12 18:22:48 +03:00
fix(combo): restore routing module load
This commit is contained in:
1
changelog.d/fixes/release-v3850-combo-module-load.md
Normal file
1
changelog.d/fixes/release-v3850-combo-module-load.md
Normal file
@@ -0,0 +1 @@
|
||||
- Restore combo routing module loading by removing a duplicate compatibility constant and an unresolved Antigravity helper import.
|
||||
@@ -618,8 +618,6 @@ export type CompatFilterOptions = {
|
||||
failOpen?: boolean;
|
||||
};
|
||||
|
||||
const HARD_COMPAT_REASONS = new Set(["tools", "vision", "structured_output"]);
|
||||
|
||||
function hasHardCapabilityFailure(reasons: string[]): boolean {
|
||||
return reasons.some((reason) => HARD_COMPAT_REASONS.has(reason));
|
||||
}
|
||||
|
||||
@@ -19,7 +19,6 @@
|
||||
* (D7a) so reset-aware tie rotation stays consistent with round-robin routing.
|
||||
*
|
||||
* @changes
|
||||
* - [2026-07-24] [Composer] - Exclude Antigravity accounts without stored projectId from reset-aware pool
|
||||
* - [2026-07-24] [Composer] - Skip quota-exhausted and rate-limited connections in reset-aware expansion
|
||||
*
|
||||
* Pure leaf: this module never imports from the combo barrel.
|
||||
@@ -45,7 +44,6 @@ import {
|
||||
type QuotaFetchCacheConfig,
|
||||
} from "./quotaScoring.ts";
|
||||
import { rankByHeadroom, type HeadroomSaturation } from "./headroomRanking.ts";
|
||||
import { preferAntigravityConnectionsWithStoredProject } from "../antigravityProjectPersistence.ts";
|
||||
import { isQuotaExhaustedForRequest } from "../../../src/domain/quotaCache.ts";
|
||||
|
||||
const RESET_AWARE_CONNECTION_CACHE_TTL_MS = 30_000;
|
||||
@@ -85,14 +83,9 @@ async function getQuotaAwareConnectionsForTarget(
|
||||
(async () => {
|
||||
try {
|
||||
const connections = await getCachedProviderConnections({ provider, isActive: true });
|
||||
let activeConnections = Array.isArray(connections)
|
||||
const activeConnections = Array.isArray(connections)
|
||||
? (connections as Array<Record<string, unknown>>)
|
||||
: [];
|
||||
if (provider === "antigravity" || provider === "agy") {
|
||||
activeConnections = preferAntigravityConnectionsWithStoredProject(
|
||||
activeConnections
|
||||
) as Array<Record<string, unknown>>;
|
||||
}
|
||||
if (
|
||||
!resetAwareConnectionCache.has(provider) &&
|
||||
resetAwareConnectionCache.size >= MAX_RESET_AWARE_CACHE
|
||||
|
||||
@@ -56,7 +56,11 @@ function capabilityEntry(limitContext: number | null) {
|
||||
};
|
||||
}
|
||||
|
||||
function capabilityEntryWithLimits(limitInput: number | null, limitContext: number | null, limitOutput = 4096) {
|
||||
function capabilityEntryWithLimits(
|
||||
limitInput: number | null,
|
||||
limitContext: number | null,
|
||||
limitOutput = 4096
|
||||
) {
|
||||
return {
|
||||
...capabilityEntry(limitContext),
|
||||
limit_input: limitInput,
|
||||
@@ -188,6 +192,26 @@ test("all known-too-small context targets still fall back to strategy order", ()
|
||||
);
|
||||
});
|
||||
|
||||
test("output-token limits remain a hard compatibility requirement", () => {
|
||||
saveModelsDevCapabilities({
|
||||
"unit-output-limit": {
|
||||
insufficient: capabilityEntryWithLimits(128_000, 128_000, 128),
|
||||
sufficient: capabilityEntryWithLimits(128_000, 128_000, 4_096),
|
||||
},
|
||||
});
|
||||
|
||||
const out = filterTargetsByRequestCompatibility(
|
||||
[target("unit-output-limit/insufficient"), target("unit-output-limit/sufficient")],
|
||||
{ messages: [{ role: "user", content: "hello" }], max_tokens: 512 },
|
||||
noopLog
|
||||
);
|
||||
|
||||
assert.deepEqual(
|
||||
out.map((entry) => entry.modelStr),
|
||||
["unit-output-limit/sufficient"]
|
||||
);
|
||||
});
|
||||
|
||||
test("known context overflow reports the largest target limit", () => {
|
||||
saveModelsDevCapabilities({
|
||||
"unit-known-context": {
|
||||
@@ -325,10 +349,10 @@ test("small input-only maxInputTokens keeps a target whose input fits even thoug
|
||||
);
|
||||
});
|
||||
|
||||
test("input-only maxInputTokens still rejects when the input itself exceeds the cap", () => {
|
||||
// The fix must not let a genuinely-too-small input cap pass. `too-small` has
|
||||
// maxInputTokens = 1, which cannot even hold the ~11-token input, so it must
|
||||
// still be dropped while the compatible target survives.
|
||||
test("input-only maxInputTokens keeps an oversized target as runtime fallback", () => {
|
||||
// Context metadata is advisory. `too-small` has maxInputTokens = 1, so the
|
||||
// known-compatible target is preferred while the catalog-too-small target
|
||||
// remains available for runtime fallback.
|
||||
saveModelsDevCapabilities({
|
||||
"unit-7039-too-small": {
|
||||
"too-small": capabilityEntryWithLimits(1, 1_000_000, 500),
|
||||
@@ -344,14 +368,14 @@ test("input-only maxInputTokens still rejects when the input itself exceeds the
|
||||
|
||||
assert.deepEqual(
|
||||
out.map((entry) => entry.modelStr),
|
||||
["unit-7039-too-small/huge"]
|
||||
["unit-7039-too-small/huge", "unit-7039-too-small/too-small"]
|
||||
);
|
||||
});
|
||||
|
||||
test("maxInputTokens defaulting to contextWindow still rejects when input + output exceeds the total window (#7039 follow-up)", () => {
|
||||
test("shared-window overflow keeps the target as runtime fallback (#7039 follow-up)", () => {
|
||||
// Shared-window model where maxInputTokens equals the total window size.
|
||||
// The input alone fits the input cap, but input + output overflows the
|
||||
// window, so the target must be rejected instead of passing on the input cap.
|
||||
// The input alone fits the input cap, but input + output exceeds the catalog
|
||||
// window. Prefer the known-compatible target without removing the fallback.
|
||||
saveModelsDevCapabilities({
|
||||
"unit-7039-window": {
|
||||
"shared-window": capabilityEntryWithLimits(400_000, 400_000, 200_000),
|
||||
@@ -367,7 +391,7 @@ test("maxInputTokens defaulting to contextWindow still rejects when input + outp
|
||||
|
||||
assert.deepEqual(
|
||||
out.map((entry) => entry.modelStr),
|
||||
["unit-7039-window/huge"]
|
||||
["unit-7039-window/huge", "unit-7039-window/shared-window"]
|
||||
);
|
||||
});
|
||||
|
||||
@@ -391,24 +415,24 @@ test("model_context_override lets a small-catalog target survive a large-context
|
||||
largeContextBody(),
|
||||
noopLog
|
||||
);
|
||||
assert.deepEqual(
|
||||
out.map((entry) => entry.modelStr).sort(),
|
||||
["unit-override/big", "unit-override/capped"]
|
||||
);
|
||||
assert.deepEqual(out.map((entry) => entry.modelStr).sort(), [
|
||||
"unit-override/big",
|
||||
"unit-override/capped",
|
||||
]);
|
||||
} finally {
|
||||
removeModelContextOverride("unit-override", "capped");
|
||||
}
|
||||
});
|
||||
|
||||
test("without an override the small-catalog target is still dropped for the large request", () => {
|
||||
test("without an override the small-catalog target remains ordered behind a compatible target", () => {
|
||||
saveModelsDevCapabilities({
|
||||
"unit-override": {
|
||||
big: capabilityEntry(1_000_000),
|
||||
capped: capabilityEntry(8_000),
|
||||
},
|
||||
});
|
||||
// No override: capped (8K) is genuinely too small and must be filtered out,
|
||||
// guarding the override read-path from masking a real too-small target.
|
||||
// No override: capped (8K) is catalog-too-small, so it stays behind the
|
||||
// known-compatible target while remaining available as a runtime fallback.
|
||||
const out = filterTargetsByRequestCompatibility(
|
||||
[target("unit-override/capped"), target("unit-override/big")],
|
||||
largeContextBody(),
|
||||
@@ -417,6 +441,6 @@ test("without an override the small-catalog target is still dropped for the larg
|
||||
|
||||
assert.deepEqual(
|
||||
out.map((entry) => entry.modelStr),
|
||||
["unit-override/big"]
|
||||
["unit-override/big", "unit-override/capped"]
|
||||
);
|
||||
});
|
||||
|
||||
9
tests/unit/combo-module-load-basered.test.ts
Normal file
9
tests/unit/combo-module-load-basered.test.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
import test from "node:test";
|
||||
import assert from "node:assert/strict";
|
||||
|
||||
test("combo routing module loads without unresolved symbols", async () => {
|
||||
const combo = await import("../../open-sse/services/combo.ts");
|
||||
|
||||
assert.equal(typeof combo.filterTargetsByRequestCompatibility, "function");
|
||||
assert.equal(typeof combo.handleComboChat, "function");
|
||||
});
|
||||
Reference in New Issue
Block a user