refactor(auto_router): drop the shunt preset, keep it as an Advanced-only toggle

Shunt is three litellm_params fields layered onto any complexity router, not
its own routing strategy. Presenting it as a peer of anthropic_family/
openai_family/etc. in the template dropdown made it look mutually exclusive
with a model-family choice when the two were never actually in conflict, and
picking it meant swapping in shunt's own hardcoded tier list.

Removes the "shunt" entry from autorouter_presets.json and the partial-fit
preset machinery that existed only to support it (isPartialFitPreset,
PARTIAL_FIT_PRESET_KEYS, hasNoUsableModelsAtAll, dropUnresolvedTierEntries,
shuntStateFromPreset). The Advanced: Shunt section on the create form is
unaffected and stays off by default: a caller composes it with whichever
model-family preset (or Custom Configuration) they actually want.
This commit is contained in:
moe-berri 2026-09-07 15:41:16 -07:00
parent 0798a83d36
commit 6a5c9234c4
8 changed files with 155 additions and 270 deletions

View file

@ -4,16 +4,33 @@
"description": "Routes across models with 1M-token context windows: Luna for simple queries, Terra for medium, Sol for complex, Opus 5 at high thinking for reasoning.",
"complexity_router_config": {
"tiers": {
"SIMPLE": ["gpt-5.6-luna"],
"MEDIUM": ["gpt-5.6-terra"],
"COMPLEX": ["gpt-5.6-sol"],
"REASONING": ["claude-opus-5"]
"SIMPLE": [
"gpt-5.6-luna"
],
"MEDIUM": [
"gpt-5.6-terra"
],
"COMPLEX": [
"gpt-5.6-sol"
],
"REASONING": [
"claude-opus-5"
]
},
"tier_model_configs": {
"REASONING": [{ "model_name": "claude-opus-5", "litellm_params": { "reasoning_effort": "high" } }]
"REASONING": [
{
"model_name": "claude-opus-5",
"litellm_params": {
"reasoning_effort": "high"
}
}
]
},
"classifier_type": "heuristic_v2",
"escalation_keywords": ["LITELLM ESCALATE"],
"escalation_keywords": [
"LITELLM ESCALATE"
],
"classification_mode": "every_request",
"session_affinity": false,
"modality_routing": false,
@ -26,16 +43,33 @@
"description": "Routes across the Claude model family: Haiku for simple queries, Sonnet for medium, Opus for complex, Opus at high thinking for reasoning.",
"complexity_router_config": {
"tiers": {
"SIMPLE": ["claude-haiku-4-5"],
"MEDIUM": ["claude-sonnet-5"],
"COMPLEX": ["claude-opus-5"],
"REASONING": ["claude-opus-5"]
"SIMPLE": [
"claude-haiku-4-5"
],
"MEDIUM": [
"claude-sonnet-5"
],
"COMPLEX": [
"claude-opus-5"
],
"REASONING": [
"claude-opus-5"
]
},
"tier_model_configs": {
"REASONING": [{ "model_name": "claude-opus-5", "litellm_params": { "reasoning_effort": "high" } }]
"REASONING": [
{
"model_name": "claude-opus-5",
"litellm_params": {
"reasoning_effort": "high"
}
}
]
},
"classifier_type": "heuristic",
"escalation_keywords": ["LITELLM ESCALATE"],
"escalation_keywords": [
"LITELLM ESCALATE"
],
"classification_mode": "every_request",
"session_affinity": false,
"modality_routing": false,
@ -48,13 +82,23 @@
"description": "Routes across the Gemini model family: Flash Lite 2.5 for simple queries, Flash Lite 3.1 for medium, Flash 3.7 for complex, Pro 3.1 for reasoning-heavy requests.",
"complexity_router_config": {
"tiers": {
"SIMPLE": ["gemini-2.5-flash-lite"],
"MEDIUM": ["gemini-3.1-flash-lite"],
"COMPLEX": ["gemini-3.7-flash"],
"REASONING": ["gemini-3.1-pro-preview"]
"SIMPLE": [
"gemini-2.5-flash-lite"
],
"MEDIUM": [
"gemini-3.1-flash-lite"
],
"COMPLEX": [
"gemini-3.7-flash"
],
"REASONING": [
"gemini-3.1-pro-preview"
]
},
"classifier_type": "heuristic",
"escalation_keywords": ["LITELLM ESCALATE"],
"escalation_keywords": [
"LITELLM ESCALATE"
],
"classification_mode": "every_request",
"session_affinity": false,
"modality_routing": false,
@ -67,14 +111,36 @@
"description": "Cost-optimized routing across providers: DeepSeek V4 Flash for simple queries, Muse Spark 1.2 at xhigh for medium, Kimi K3 at max for complex, Claude Opus 5 for reasoning. An LLM classifier with the agentic rubric assigns tiers.",
"complexity_router_config": {
"tiers": {
"SIMPLE": ["deepseek-v4-flash"],
"MEDIUM": ["muse-spark-1.2"],
"COMPLEX": ["kimi-k3"],
"REASONING": ["claude-opus-5"]
"SIMPLE": [
"deepseek-v4-flash"
],
"MEDIUM": [
"muse-spark-1.2"
],
"COMPLEX": [
"kimi-k3"
],
"REASONING": [
"claude-opus-5"
]
},
"tier_model_configs": {
"MEDIUM": [{ "model_name": "muse-spark-1.2", "litellm_params": { "reasoning_effort": "xhigh" } }],
"COMPLEX": [{ "model_name": "kimi-k3", "litellm_params": { "reasoning_effort": "max" } }]
"MEDIUM": [
{
"model_name": "muse-spark-1.2",
"litellm_params": {
"reasoning_effort": "xhigh"
}
}
],
"COMPLEX": [
{
"model_name": "kimi-k3",
"litellm_params": {
"reasoning_effort": "max"
}
}
]
},
"classifier_type": "llm",
"classifier_llm_config": {
@ -83,7 +149,9 @@
"classification_rubric": "agentic"
},
"classifier_context_window_size": 0,
"escalation_keywords": ["LITELLM ESCALATE"],
"escalation_keywords": [
"LITELLM ESCALATE"
],
"classification_mode": "every_request",
"session_affinity": false,
"modality_routing": false,
@ -96,41 +164,38 @@
"description": "Routes across the GPT model family: Luna for simple queries, Terra for medium, Sol for complex, Sol at xhigh thinking for reasoning.",
"complexity_router_config": {
"tiers": {
"SIMPLE": ["gpt-5.6-luna"],
"MEDIUM": ["gpt-5.6-terra"],
"COMPLEX": ["gpt-5.6-sol"],
"REASONING": ["gpt-5.6-sol"]
"SIMPLE": [
"gpt-5.6-luna"
],
"MEDIUM": [
"gpt-5.6-terra"
],
"COMPLEX": [
"gpt-5.6-sol"
],
"REASONING": [
"gpt-5.6-sol"
]
},
"tier_model_configs": {
"REASONING": [{ "model_name": "gpt-5.6-sol", "litellm_params": { "reasoning_effort": "xhigh" } }]
"REASONING": [
{
"model_name": "gpt-5.6-sol",
"litellm_params": {
"reasoning_effort": "xhigh"
}
}
]
},
"classifier_type": "heuristic",
"escalation_keywords": ["LITELLM ESCALATE"],
"escalation_keywords": [
"LITELLM ESCALATE"
],
"classification_mode": "every_request",
"session_affinity": false,
"modality_routing": false,
"modality_pin_override": false,
"deployment_affinity": true
}
},
"shunt": {
"label": "Shunt",
"description": "Bounds large file/shell reads and delegates bulk-read and boilerplate-codegen tool calls to a cheap model, based on the shunt technique for AI coding agents (https://engineering.atspotify.com/2026/9/portal-by-spotify-cut-my-claude-code-token-usage-by-90). Ported server-side: works with any client, no plugin install. classifier_type is heuristic so this never counts against the licensed heuristic_v2 limit.",
"complexity_router_config": {
"tiers": {
"SIMPLE": ["claude-haiku-4-5", "gpt-5.6-luna", "gemini-2.5-flash-lite", "deepseek-v4-flash"],
"MEDIUM": ["claude-sonnet-5", "gpt-5.6-terra", "gemini-3.1-flash-lite"],
"COMPLEX": ["claude-opus-5", "gpt-5.6-sol", "gemini-3.7-flash"],
"REASONING": ["claude-opus-5", "gpt-5.6-sol", "gemini-3.1-pro-preview"]
},
"classifier_type": "heuristic",
"escalation_keywords": ["LITELLM ESCALATE"],
"classification_mode": "every_request",
"session_affinity": false,
"modality_routing": false,
"modality_pin_override": false,
"deployment_affinity": true
},
"auto_router_shunt_min_lines": 350
}
}

View file

@ -771,7 +771,6 @@ describe("AddAutoRouterTab", () => {
"Gemini Family",
"Lite",
"OpenAI Family",
"Shunt",
"Custom Configuration",
]);
});
@ -1028,68 +1027,11 @@ describe("AddAutoRouterTab", () => {
});
});
// Shunt is a partial-fit preset (see isPartialFitPreset in @/lib/autorouter_presets): unlike
// the family presets above, it stays selectable even when none of its own named tier models
// are available, as long as the caller has some chat model at all.
describe("shunt preset", () => {
it("stays selectable with only unrelated chat models, unlike an ordinary family preset", async () => {
mockFetchAvailableModels.mockResolvedValue([{ model_group: "unknown-model-a", mode: "chat" }]);
renderWithProviders(<Harness />);
openTemplateDropdown();
await waitFor(() => expect(optionByLabel("Anthropic Family")).toHaveTextContent("Missing:"));
await waitFor(() => expect(isOptionDisabled(optionByLabel("Shunt")!)).toBe(false));
});
it("greys out only when the caller has no chat model at all", async () => {
mockFetchAvailableModels.mockResolvedValue([]);
renderWithProviders(<Harness />);
openTemplateDropdown();
await waitFor(() => expect(isOptionDisabled(optionByLabel("Shunt")!)).toBe(true));
});
it("expands detailed configuration with empty tiers when none of its own models resolve", async () => {
const user = userEvent.setup();
const actual = await vi.importActual<typeof import("./build_complexity_router_config")>(
"./build_complexity_router_config",
);
vi.mocked(getMissingTiersError).mockImplementation(actual.getMissingTiersError);
mockFetchAvailableModels.mockResolvedValue([{ model_group: "unknown-model-a", mode: "chat" }]);
renderWithProviders(<Harness />);
await waitForPresetEnabled("Shunt");
await selectTemplate("Shunt");
// Detailed Configuration auto-expands (none of Shunt's own tier models resolved, the same
// "needs your input" signal a deployment-matched family preset also expands for), and the
// submit stays blocked until a caller fills in a model for every tier - the same gate
// Custom Configuration is held to, since an unfilled tier is unfilled either way.
expect(screen.getByText("Complexity Tier Configuration")).toBeInTheDocument();
await user.type(screen.getByPlaceholderText(/smart_router/i), "shunt-empty-tiers");
expect(screen.getByRole("button", { name: /add auto router/i })).toBeDisabled();
});
it("carries the bounded-read threshold through to the create payload", async () => {
const user = userEvent.setup();
vi.mocked(getMissingTiersError).mockReturnValue(null);
mockFetchAvailableModels.mockResolvedValue([{ model_group: "some-chat-model", mode: "chat" }]);
renderWithProviders(<Harness />);
await waitForPresetEnabled("Shunt");
await selectTemplate("Shunt");
await user.type(screen.getByPlaceholderText(/smart_router/i), "shunt-router");
await user.click(screen.getByRole("button", { name: /add auto router/i }));
await waitFor(() => expect(handleAddAutoRouterSubmit).toHaveBeenCalled());
const submitted = vi.mocked(handleAddAutoRouterSubmit).mock.calls.at(-1)?.[0];
expect(submitted?.auto_router_shunt_min_lines).toBe(350);
});
it("omits shunt fields entirely for a router built from Custom Configuration", async () => {
// Shunt is not a preset: it's an Advanced toggle available on any router, off by default, so
// these exercise the toggle directly against an ordinary family preset rather than a preset
// of its own.
describe("shunt (Advanced)", () => {
it("omits shunt fields entirely until a caller opens the section and sets a threshold", async () => {
const user = userEvent.setup();
vi.mocked(getMissingTiersError).mockReturnValue(null);
mockFetchAvailableModels.mockResolvedValue(ALL_FAMILY_MODELS);
@ -1106,17 +1048,45 @@ describe("AddAutoRouterTab", () => {
expect(submitted?.auto_router_shunt_min_lines).toBeUndefined();
});
it("carries the bounded-read threshold through to the create payload once set", async () => {
const user = userEvent.setup();
vi.mocked(getMissingTiersError).mockReturnValue(null);
mockFetchAvailableModels.mockResolvedValue(ALL_FAMILY_MODELS);
renderWithProviders(<Harness />);
await waitForPresetEnabled("Anthropic Family");
await selectTemplate("Anthropic Family");
await user.type(screen.getByPlaceholderText(/smart_router/i), "shunt-router");
expandDetailedConfiguration();
await user.click(screen.getByText("Advanced: Shunt"));
const threshold = screen.getByRole("spinbutton", { name: "Bounded-read threshold" });
fireEvent.change(threshold, { target: { value: "350" } });
await user.click(screen.getByRole("button", { name: /add auto router/i }));
await waitFor(() => expect(handleAddAutoRouterSubmit).toHaveBeenCalled());
const submitted = vi.mocked(handleAddAutoRouterSubmit).mock.calls.at(-1)?.[0];
expect(submitted?.auto_router_shunt_min_lines).toBe(350);
});
it("carries a chosen worker model through to the create payload", async () => {
const user = userEvent.setup();
vi.mocked(getMissingTiersError).mockReturnValue(null);
mockFetchAvailableModels.mockResolvedValue([{ model_group: "some-chat-model", mode: "chat" }]);
mockFetchAvailableModels.mockResolvedValue([
...ALL_FAMILY_MODELS,
{ model_group: "some-chat-model", mode: "chat" },
]);
renderWithProviders(<Harness />);
await waitForPresetEnabled("Shunt");
await selectTemplate("Shunt");
await waitForPresetEnabled("Anthropic Family");
await selectTemplate("Anthropic Family");
await user.type(screen.getByPlaceholderText(/smart_router/i), "shunt-with-worker-model");
expandDetailedConfiguration();
await user.click(screen.getByText("Advanced: Shunt"));
const threshold = screen.getByRole("spinbutton", { name: "Bounded-read threshold" });
fireEvent.change(threshold, { target: { value: "350" } });
await chooseSelectOption(user, screen.getByRole("combobox", { name: "Bulk-read model" }), "some-chat-model");
await user.click(screen.getByRole("button", { name: /add auto router/i }));
@ -1296,12 +1266,8 @@ describe("AddAutoRouterTab", () => {
expect(isOptionDisabled(optionByLabel("Anthropic Family")!)).toBe(false);
});
const labels = visibleOptions().map((option) => option.querySelector(".font-medium")?.textContent);
// Shunt is a partial-fit preset (see isPartialFitPreset): it stays available as long as SOME
// chat model is registered, regardless of whether it's one of Shunt's own named models, so it
// sorts alongside Anthropic Family here rather than with the family presets that stay disabled.
expect(labels).toEqual([
"Anthropic Family",
"Shunt",
"1M Context",
"Gemini Family",
"Lite",

View file

@ -41,7 +41,6 @@ import {
type AutoRouterShuntState,
buildAutoRouterShuntParams,
DEFAULT_AUTO_ROUTER_SHUNT,
shuntStateFromPreset,
} from "./buildAutoRouterShunt";
import { DEFAULT_MATCH_THRESHOLD } from "./SemanticKeywordMatching";
import {
@ -69,8 +68,6 @@ import {
buildPresetPrefill,
buildModelAvailability,
deploymentRefsFromModelInfo,
hasNoUsableModelsAtAll,
isPartialFitPreset,
ModelAvailability,
PresetPrefill,
AutoRouterPreset,
@ -299,23 +296,10 @@ const AddAutoRouterTab: React.FC<AddAutoRouterTabProps> = ({
// have been applied: while loading we withhold selection rather than let a caller pick a preset
// whose models we cannot yet verify, and a failed fetch leaves every preset unverifiable. This
// makes the load-race (pick during loading, then discover a missing model) unrepresentable.
//
// A partial-fit preset (isPartialFitPreset) is gated differently: its interception settings
// apply regardless of tier models, so it greys out only when the caller has no usable chat
// model at all, never merely because none of its own named models match the caller's fleet.
// Selecting it then leaves whichever tiers didn't resolve empty for the caller to fill in
// (see handlePresetChange), rather than blocking selection the way every other preset does.
const presetAvailability = React.useCallback(
(preset: AutoRouterPreset): PresetAvailability => {
if (modelsLoading) return { kind: "loading" };
if (modelsUnverifiable) return { kind: "unverifiable" };
if (isPartialFitPreset(preset)) {
if (hasNoUsableModelsAtAll(availability)) return { kind: "missing_models", models: ["any chat model"] };
// Expand Detailed Configuration when at least one tier didn't resolve to any of the
// preset's own models, the same "needs your input" signal viaDeployments already means
// for a fully-resolving preset - here it can mean the caller has to fill a tier by hand.
return { kind: "available", viaDeployments: getMissingModelsInPreset(preset, availability).length > 0 };
}
const missing = getMissingModelsInPreset(preset, availability);
if (missing.length > 0) return { kind: "missing_models", models: missing };
return {
@ -380,12 +364,7 @@ const AddAutoRouterTab: React.FC<AddAutoRouterTabProps> = ({
if (presetState.kind !== "available") return;
setSelectedPreset(presetKey);
applyPrefill(
buildPresetPrefill(preset.complexity_router_config, availability, {
dropUnresolvedTierEntries: isPartialFitPreset(preset),
}),
shuntStateFromPreset(preset.auto_router_shunt_min_lines),
);
applyPrefill(buildPresetPrefill(preset.complexity_router_config, availability));
setDetailsExpanded(presetState.viaDeployments);
};

View file

@ -1,9 +1,4 @@
import {
buildAutoRouterShuntParams,
DEFAULT_AUTO_ROUTER_SHUNT,
hydrateAutoRouterShunt,
shuntStateFromPreset,
} from "./buildAutoRouterShunt";
import { buildAutoRouterShuntParams, DEFAULT_AUTO_ROUTER_SHUNT, hydrateAutoRouterShunt } from "./buildAutoRouterShunt";
describe("buildAutoRouterShuntParams", () => {
it("omits all three keys when the threshold was never configured", () => {
@ -72,13 +67,3 @@ describe("hydrateAutoRouterShunt", () => {
expect(rebuilt).toEqual(original);
});
});
describe("shuntStateFromPreset", () => {
it("stays untouched when the preset carries no threshold", () => {
expect(shuntStateFromPreset(undefined)).toEqual(DEFAULT_AUTO_ROUTER_SHUNT);
});
it("arms with the preset's threshold and no worker-model overrides", () => {
expect(shuntStateFromPreset(350)).toEqual({ minLines: 350, bulkReadModel: undefined, codeWriteModel: undefined });
});
});

View file

@ -51,10 +51,3 @@ export const hydrateAutoRouterShunt = (litellmParams: {
codeWriteModel: litellmParams.auto_router_shunt_code_write_model ?? undefined,
};
};
/** From a partial-fit preset's own top-level auto_router_shunt_min_lines (see
* autorouter_presets.ts's AutoRouterPreset.auto_router_shunt_min_lines), or untouched. */
export const shuntStateFromPreset = (presetMinLines: number | undefined): AutoRouterShuntState =>
presetMinLines === undefined
? DEFAULT_AUTO_ROUTER_SHUNT
: { minLines: presetMinLines, bulkReadModel: undefined, codeWriteModel: undefined };

View file

@ -13,8 +13,6 @@ import {
buildModelAvailability,
deploymentRefsFromModelInfo,
normalizeModelName,
isPartialFitPreset,
hasNoUsableModelsAtAll,
} from "./autorouter_presets";
import { DEFAULT_MATCH_THRESHOLD } from "@/components/add_model/SemanticKeywordMatching";
import { DEFAULT_ESCALATION_KEYWORDS } from "@/components/add_model/EscalationKeywords";
@ -35,7 +33,6 @@ describe("autorouter_presets", () => {
"Gemini Family",
"Lite",
"OpenAI Family",
"Shunt",
]);
// Every preset carries all four fields the UI relies on; a JSON typo dropping one fails here.
for (const p of presets) {
@ -825,72 +822,4 @@ describe("autorouter_presets", () => {
expect(prefill.complexityRouterConfig.tier_model_params).toBeUndefined();
});
});
describe("isPartialFitPreset", () => {
it("marks the shunt preset as partial-fit", () => {
expect(isPartialFitPreset(getPresetByKey("shunt")!)).toBe(true);
});
it("does not mark an ordinary family preset as partial-fit", () => {
expect(isPartialFitPreset(getPresetByKey("anthropic_family")!)).toBe(false);
});
});
describe("hasNoUsableModelsAtAll", () => {
it("is true when the caller has registered no chat models", () => {
expect(hasNoUsableModelsAtAll(groupsOnly([]))).toBe(true);
});
it("is false as soon as any model group is registered, whether or not the preset names it", () => {
expect(hasNoUsableModelsAtAll(groupsOnly(["some-unrelated-model"]))).toBe(false);
});
});
describe("buildPresetPrefill with dropUnresolvedTierEntries", () => {
const config = {
tiers: {
SIMPLE: ["model-a", "model-b"],
MEDIUM: ["model-c"],
COMPLEX: ["model-d"],
REASONING: ["model-e"],
},
classifier_type: "heuristic" as const,
classification_mode: "every_request" as const,
session_affinity: false,
deployment_affinity: true,
};
it("drops a tier entry that fails to resolve instead of keeping the preset's literal string", () => {
const prefill = buildPresetPrefill(config, groupsOnly(["model-a"]), { dropUnresolvedTierEntries: true });
expect(prefill.complexityRouterConfig.tiers).toEqual({
SIMPLE: ["model-a"],
MEDIUM: [],
COMPLEX: [],
REASONING: [],
});
});
it("keeps every resolved entry in a tier that partially resolves", () => {
const prefill = buildPresetPrefill(config, groupsOnly(["model-a", "model-b"]), {
dropUnresolvedTierEntries: true,
});
expect(prefill.complexityRouterConfig.tiers.SIMPLE).toEqual(["model-a", "model-b"]);
});
it("defaults to the literal-fallback behavior when the option is omitted", () => {
const prefill = buildPresetPrefill(config, groupsOnly([]));
expect(prefill.complexityRouterConfig.tiers.SIMPLE).toEqual(["model-a", "model-b"]);
});
it("still rewrites a resolved entry to the caller's registered spelling", () => {
const versioned = {
...config,
tiers: { ...config.tiers, SIMPLE: ["claude-sonnet-4.5"] },
};
const prefill = buildPresetPrefill(versioned, groupsOnly(["claude-sonnet-4-5"]), {
dropUnresolvedTierEntries: true,
});
expect(prefill.complexityRouterConfig.tiers.SIMPLE).toEqual(["claude-sonnet-4-5"]);
});
});
});

View file

@ -28,22 +28,8 @@ export interface AutoRouterPreset {
label: string;
description: string;
complexity_router_config: ComplexityRouterConfigPayload;
// Present only on shunt-shaped presets: arms the shunt guardrail's bounded-read threshold.
// Lives at this top level, not inside complexity_router_config, because it maps to litellm_params
// fields (auto_router_shunt_*) rather than complexity_router_config settings.
auto_router_shunt_min_lines?: number;
}
// Presets whose tier models are suggestions rather than a hard requirement: shunt's value is the
// I/O interception it arms regardless of which tier models resolve, so a preset carrying this key
// should never grey out purely because none of ITS named tier models match the caller's fleet -
// only when the caller has no usable chat model at all. Keyed by preset.key rather than a field on
// the catalog JSON: this is a dashboard policy decision about how strictly to gate selection, not
// something a preset publisher should be able to opt into by editing the catalog.
const PARTIAL_FIT_PRESET_KEYS: ReadonlySet<string> = new Set(["shunt"]);
export const isPartialFitPreset = (preset: AutoRouterPreset): boolean => PARTIAL_FIT_PRESET_KEYS.has(preset.key);
export type AutoRouterPresetsResponse = Record<string, Omit<AutoRouterPreset, "key">>;
export const hydratePresets = (raw: AutoRouterPresetsResponse): AutoRouterPreset[] =>
@ -188,11 +174,6 @@ export const getRequiredModelsInPreset = (preset: AutoRouterPreset): Set<string>
export const getMissingModelsInPreset = (preset: AutoRouterPreset, availability: ModelAvailability): string[] =>
getMissingModels(preset.complexity_router_config, availability);
// The gate a partial-fit preset (see isPartialFitPreset) is selectable under: the proxy has to
// have registered SOME chat model, but not specifically one this preset names - unlike
// getMissingModelsInPreset's all-or-nothing check, which is still what every other preset uses.
export const hasNoUsableModelsAtAll = (availability: ModelAvailability): boolean => availability.modelGroups.size === 0;
// Checks the config actually being built (whether it arrived via a preset prefill or was typed by
// hand - the two are indistinguishable once the caller has started editing), not a preset's
// original bundled model list. Only counts classifier_llm_config/embedding_model as referenced
@ -257,22 +238,9 @@ export const buildEmptyPrefill = (): PresetPrefill => ({
export const buildPresetPrefill = (
config: ComplexityRouterConfigPayload,
availability: ModelAvailability,
// Partial-fit presets (see isPartialFitPreset) drop a tier entry that fails to resolve
// instead of keeping the preset's own unresolved string, so a caller who owns none of the
// preset's named models for a tier gets an empty picker to fill in rather than a config that
// silently names a model they don't have. Off by default: every other preset call site
// already guarantees full resolution before calling this (presetAvailability), so keeping the
// literal fallback there is unreachable, not a behavior change.
{ dropUnresolvedTierEntries = false }: { dropUnresolvedTierEntries?: boolean } = {},
): PresetPrefill => {
const resolve = (model: string): string => resolveAvailableModel(model, availability) ?? model;
const resolveTier = (models: string[]): string[] =>
dropUnresolvedTierEntries
? models.flatMap((model) => {
const resolved = resolveAvailableModel(model, availability);
return resolved ? [resolved] : [];
})
: models.map(resolve);
const resolveTier = (models: string[]): string[] => models.map(resolve);
// Params key on the model name the preset spells while every tier entry is rewritten to the
// caller's registered spelling, so the keys have to be rewritten the same way. Otherwise
// serializeTierModelConfigs drops them for naming a model the tier no longer holds.

File diff suppressed because one or more lines are too long