fix(ui): keep the add-provider tab after auto-routers, and drop aliases for failed rows

The tab list put add-provider ahead of auto-routers, which reordered the tabs an
existing test pins. It now sits after auto-routers, where the admin-only tabs start

The wizard also wrote a model_group_alias entry for every discovered row it tried,
including rows whose model creation had just failed, leaving an alias pointing at a
deployment that does not exist. Alias additions are now taken from the rows that
actually got created
This commit is contained in:
derhornspieler 2026-08-23 12:42:32 -04:00
parent b0a28b2e09
commit cc18f804f8
3 changed files with 41 additions and 2 deletions

View file

@ -105,8 +105,8 @@ export default function ModelsAndEndpointsPage() {
() => [
"",
...(canCreate ? (["add"] as const) : []),
...(isAdmin ? (["add-provider"] as const) : []),
...(isAdmin || canCreate ? (["auto-routers"] as const) : []),
...(isAdmin ? (["add-provider"] as const) : []),
...(isAdmin
? (["llm-credentials", "pass-through", "health", "retry-settings", "model-group-alias", "price-data"] as const)
: []),

View file

@ -200,4 +200,42 @@ describe("AddProviderPanel", () => {
expect(await screen.findByText(/claude-3-opus: skipped/)).toBeInTheDocument();
expect(createProviderModelCall).not.toHaveBeenCalled();
});
it("does not persist an alias for a row whose model creation failed", async () => {
discoverProviderModelsCall.mockResolvedValue({ models: ["claude-3-opus", "claude-3-haiku"] });
createProviderModelCall.mockImplementation(async (_token: string, payload: { model_name: string }) => {
if (payload.model_name === "claude-3-haiku") {
throw new Error("upstream rejected");
}
return { model_id: "new-id" };
});
const { user } = await setup();
await chooseProvider(user, "Anthropic");
await user.type(screen.getByLabelText("Credential name"), "anthropic-prod");
await user.click(screen.getByRole("button", { name: /Next/ }));
await user.type(await screen.findByLabelText("API Key"), "sk-ant-test");
await user.click(screen.getByRole("button", { name: "Save credential" }));
expect(await screen.findByText("claude-3-opus")).toBeInTheDocument();
const opusAltNames = rowFor("claude-3-opus").getByRole("combobox");
await user.type(opusAltNames, "gpt-4o");
await user.click(await screen.findByText('Create "gpt-4o"'));
const haikuAltNames = rowFor("claude-3-haiku").getByRole("combobox");
await user.type(haikuAltNames, "gpt-4o-mini");
await user.click(await screen.findByText('Create "gpt-4o-mini"'));
await user.click(screen.getByRole("button", { name: "Create 2 models" }));
expect(await screen.findByText(/claude-3-opus: created/)).toBeInTheDocument();
expect(await screen.findByText(/claude-3-haiku: failed/)).toBeInTheDocument();
await waitFor(() =>
expect(setCallbacksCall).toHaveBeenCalledWith("test-access-token", {
router_settings: { model_group_alias: { "gpt-4o": "claude-3-opus" } },
}),
);
});
});

View file

@ -257,7 +257,8 @@ export default function AddProviderPanel() {
}
setCreationResults(results);
const additions = aliasAdditionsFromRows(rows);
const failedRowIds = new Set(results.filter((r) => r.status === "failed").map((r) => r.row.id));
const additions = aliasAdditionsFromRows(rows.filter((row) => !failedRowIds.has(row.id)));
if (additions.length > 0) {
try {
const config = await getCallbacksCall(accessToken, "", "");