From 522bffb8d221d7a781b7d58160c7fdef47d9f21f Mon Sep 17 00:00:00 2001 From: cyphercodes Date: Thu, 21 May 2026 18:45:28 +0300 Subject: [PATCH] fix: avoid stack overflow expanding wildcard models --- .../fetch_available_models_team_key.test.tsx | 24 ++++++++++++++++++- .../fetch_available_models_team_key.tsx | 6 ++--- 2 files changed, 25 insertions(+), 5 deletions(-) diff --git a/ui/litellm-dashboard/src/components/key_team_helpers/fetch_available_models_team_key.test.tsx b/ui/litellm-dashboard/src/components/key_team_helpers/fetch_available_models_team_key.test.tsx index 1541472cc84..7b2fbd31de0 100644 --- a/ui/litellm-dashboard/src/components/key_team_helpers/fetch_available_models_team_key.test.tsx +++ b/ui/litellm-dashboard/src/components/key_team_helpers/fetch_available_models_team_key.test.tsx @@ -1,6 +1,6 @@ import { describe, expect, it } from "vitest"; -import { getModelDisplayName } from "./fetch_available_models_team_key"; +import { getModelDisplayName, unfurlWildcardModelsInList } from "./fetch_available_models_team_key"; describe("getModelDisplayName", () => { it("should return display label for all proxy models", () => { @@ -11,3 +11,25 @@ describe("getModelDisplayName", () => { expect(getModelDisplayName("openai/*")).toBe("All openai models"); }); }); + +describe("unfurlWildcardModelsInList", () => { + it("should expand wildcard models while preserving display names and removing duplicates", () => { + expect( + unfurlWildcardModelsInList( + ["openai/*", "anthropic/claude-4", "openai/gpt-4"], + ["openai/gpt-4", "openai/gpt-4o", "anthropic/claude-4"], + ), + ).toEqual(["openai/*", "openai/gpt-4", "openai/gpt-4o", "anthropic/claude-4"]); + }); + + it("should not exceed the call stack when expanding a wildcard with many matching models", () => { + const allModels = Array.from({ length: 150_000 }, (_, index) => `openai/model-${index}`); + + const result = unfurlWildcardModelsInList(["openai/*"], allModels); + + expect(result).toHaveLength(allModels.length + 1); + expect(result[0]).toBe("openai/*"); + expect(result[1]).toBe("openai/model-0"); + expect(result.at(-1)).toBe("openai/model-149999"); + }); +}); diff --git a/ui/litellm-dashboard/src/components/key_team_helpers/fetch_available_models_team_key.tsx b/ui/litellm-dashboard/src/components/key_team_helpers/fetch_available_models_team_key.tsx index 6aa1c5b483f..9d30c4746f7 100644 --- a/ui/litellm-dashboard/src/components/key_team_helpers/fetch_available_models_team_key.tsx +++ b/ui/litellm-dashboard/src/components/key_team_helpers/fetch_available_models_team_key.tsx @@ -49,8 +49,6 @@ export const getModelDisplayName = (model: string) => { export const unfurlWildcardModelsInList = (teamModels: string[], allModels: string[]): string[] => { const wildcardDisplayNames: string[] = []; const expandedModels: string[] = []; - console.log("teamModels", teamModels); - console.log("allModels", allModels); teamModels.forEach((teamModel) => { if (teamModel.endsWith("/*")) { @@ -59,7 +57,7 @@ export const unfurlWildcardModelsInList = (teamModels: string[], allModels: stri // Find all models that start with this provider const matchingModels = allModels.filter((model) => model.startsWith(provider + "/")); - expandedModels.push(...matchingModels); + matchingModels.forEach((model) => expandedModels.push(model)); wildcardDisplayNames.push(teamModel); } else { expandedModels.push(teamModel); @@ -67,5 +65,5 @@ export const unfurlWildcardModelsInList = (teamModels: string[], allModels: stri }); // Combine arrays with wildcard display names first, then remove duplicates - return [...wildcardDisplayNames, ...expandedModels].filter((item, index, array) => array.indexOf(item) === index); + return Array.from(new Set([...wildcardDisplayNames, ...expandedModels])); };