mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-08-01 12:52:11 +03:00
fix: add fuzzy auto-combo routing for 'auto/*' model prefix (#2010)
Integrated into release/v3.8.0
This commit is contained in:
@@ -12,7 +12,7 @@ export function DocsSidebarClient({ mobileOnly = false }: { mobileOnly?: boolean
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
|
||||
// Extract slug from pathname (e.g., /docs/setup-guide -> setup-guide)
|
||||
const currentSlug = pathname.split("/").filter(Boolean).pop() || "";
|
||||
const currentSlug = pathname.split("/").filter(Boolean).pop();
|
||||
|
||||
const isActive = (slug: string) => currentSlug === slug;
|
||||
|
||||
|
||||
@@ -278,7 +278,21 @@ export async function handleChat(request: any, clientRawRequest: any = null) {
|
||||
|
||||
// Check if model is a combo (has multiple models with fallback)
|
||||
telemetry.startPhase("resolve");
|
||||
const combo: any = await getComboForModel(resolvedModelStr);
|
||||
let combo: any = await getComboForModel(resolvedModelStr);
|
||||
|
||||
// "auto" prefix fuzzy matching: "auto/fast" → "auto/best-fast", etc.
|
||||
// parseModel splits "auto/fast" into provider="auto" which isn't a real provider.
|
||||
if (!combo && resolvedModelStr.startsWith("auto/")) {
|
||||
const suffix = resolvedModelStr.slice(5);
|
||||
for (const candidate of [`auto/best-${suffix}`, `auto/${suffix}`]) {
|
||||
combo = await getComboForModel(candidate);
|
||||
if (combo) {
|
||||
log.info("ROUTING", `"${resolvedModelStr}" → combo "${candidate}" (auto fuzzy)`);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (combo) {
|
||||
log.info(
|
||||
"CHAT",
|
||||
@@ -493,6 +507,53 @@ async function handleSingleModelChat(
|
||||
);
|
||||
if (resolved.error) return resolved.error;
|
||||
|
||||
// Safety net: if auto-combo resolution returned a combo object, redirect
|
||||
// to combo flow. This handles the case where the auto-fuzzy match in
|
||||
// resolveModelOrError found a combo but the main handler's combo lookup missed it.
|
||||
if ((resolved as any).combo) {
|
||||
const redirectCombo = (resolved as any).combo;
|
||||
log.info("ROUTING", `Auto-combo redirect from handleSingleModelChat for "${modelStr}"`);
|
||||
log.info("ROUTING", `Auto-combo redirect to combo flow for "${modelStr}"`);
|
||||
return handleComboChat({
|
||||
body,
|
||||
combo: redirectCombo,
|
||||
handleSingleModel: (
|
||||
b: any,
|
||||
m: string,
|
||||
target?: {
|
||||
connectionId?: string | null;
|
||||
executionKey?: string | null;
|
||||
stepId?: string | null;
|
||||
}
|
||||
) =>
|
||||
handleSingleModelChat(
|
||||
b,
|
||||
m,
|
||||
clientRawRequest,
|
||||
request,
|
||||
redirectCombo.name ?? modelStr,
|
||||
apiKeyInfo,
|
||||
telemetry,
|
||||
{
|
||||
sessionId: "", // safety-net redirect doesn't have session context
|
||||
forceLiveComboTest: false,
|
||||
forcedConnectionId: null,
|
||||
allowedConnectionIds: null,
|
||||
comboStepId: null,
|
||||
comboExecutionKey: null,
|
||||
},
|
||||
redirectCombo.strategy ?? "priority",
|
||||
false
|
||||
),
|
||||
isModelAvailable: async () => true,
|
||||
log,
|
||||
settings: {},
|
||||
allCombos: [],
|
||||
relayOptions: undefined,
|
||||
signal: request?.signal ?? null,
|
||||
});
|
||||
}
|
||||
|
||||
const { provider, model, sourceFormat, targetFormat, extendedContext } = resolved;
|
||||
const forceLiveComboTest = runtimeOptions.forceLiveComboTest === true;
|
||||
const hasForcedConnection =
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { getModelInfo } from "../services/model";
|
||||
import { getModelInfo, getComboForModel } from "../services/model";
|
||||
import { clearAccountError, markAccountUnavailable } from "../services/auth";
|
||||
import * as log from "../utils/logger";
|
||||
import { updateProviderCredentials } from "../services/tokenRefresh";
|
||||
@@ -130,6 +130,46 @@ export async function resolveModelOrError(
|
||||
}
|
||||
}
|
||||
|
||||
// "auto" is a combo prefix, not a provider. parseModel("auto/fast") splits it into
|
||||
// provider="auto" model="fast" — redirect to matching combo before credential lookup fails.
|
||||
if (modelInfo.provider === "auto") {
|
||||
const exactCombo = await getComboForModel(modelStr);
|
||||
if (exactCombo) {
|
||||
log.info("ROUTING", `"auto" provider → combo "${modelStr}"`);
|
||||
return { combo: exactCombo, provider: "auto", model: modelInfo.model };
|
||||
}
|
||||
|
||||
// Fuzzy: "fast" → "auto/best-fast", "chat" → "auto/best-chat"
|
||||
const suffix = modelInfo.model || "";
|
||||
for (const candidate of [`auto/best-${suffix}`, `auto/${suffix}`]) {
|
||||
const fuzzyCombo = await getComboForModel(candidate);
|
||||
if (fuzzyCombo) {
|
||||
log.info("ROUTING", `"auto/${suffix}" → combo "${candidate}" (fuzzy)`);
|
||||
return { combo: fuzzyCombo, provider: "auto", model: suffix };
|
||||
}
|
||||
}
|
||||
|
||||
// List available auto/* combos in error
|
||||
const available: string[] = [];
|
||||
try {
|
||||
const { getCombos } = await import("@/lib/localDb");
|
||||
const all = await getCombos();
|
||||
for (const c of all) {
|
||||
if (c.name?.startsWith("auto/")) available.push(c.name);
|
||||
}
|
||||
} catch {
|
||||
/* DB unavailable */
|
||||
}
|
||||
|
||||
const hint =
|
||||
available.length > 0
|
||||
? ` Available auto combos: ${available.join(", ")}`
|
||||
: " No auto combos configured — create one in the Dashboard.";
|
||||
const message = `Model '${modelStr}' is not a valid combo or provider.${hint}`;
|
||||
log.warn("CHAT", message, { model: modelStr });
|
||||
return { error: errorResponse(HTTP_STATUS.BAD_REQUEST, message) };
|
||||
}
|
||||
|
||||
if (!modelInfo.provider) {
|
||||
if ((modelInfo as any).errorType === "ambiguous_model") {
|
||||
// Family disambiguation: if the model name begins with a known
|
||||
|
||||
Reference in New Issue
Block a user