fix: add fuzzy auto-combo routing for 'auto/*' model prefix (#2010)

Integrated into release/v3.8.0
This commit is contained in:
Paijo
2026-05-07 18:49:16 +07:00
committed by GitHub
parent eb2579ec0a
commit 7214efa86e
3 changed files with 104 additions and 3 deletions

View File

@@ -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;

View File

@@ -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 =

View File

@@ -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