diff --git a/src/lib/llm/models.ts b/src/lib/llm/models.ts index 5ad6fca..6d0fdb3 100644 --- a/src/lib/llm/models.ts +++ b/src/lib/llm/models.ts @@ -75,14 +75,19 @@ export async function listLlmModels(options?: { } const models = normalizeLlmModelsPayload(await response.json()); - const merged = new Map(); - merged.set(fallbackModel, fallbackOption); - for (const model of models) merged.set(model.id, model); + + if (models.length === 0) { + return { models: [fallbackOption], fallbackModel, degraded: true }; + } + + if (!models.some((model) => model.id === fallbackModel)) { + return { models: [fallbackOption], fallbackModel, degraded: false }; + } return { - models: [...merged.values()].sort((a, b) => a.id.localeCompare(b.id)), + models, fallbackModel, - degraded: models.length === 0, + degraded: false, }; } catch { return { models: [fallbackOption], fallbackModel, degraded: true }; diff --git a/tests/unit/llm-models.test.ts b/tests/unit/llm-models.test.ts index 2bc2879..ae8ba53 100644 --- a/tests/unit/llm-models.test.ts +++ b/tests/unit/llm-models.test.ts @@ -57,13 +57,13 @@ describe("isValidLlmModelId", () => { }); describe("listLlmModels", () => { - it("fetches provider models with API key auth and includes the fallback model", async () => { + it("fetches provider models with API key auth when the fallback is advertised", async () => { const requests: Request[] = []; const result = await listLlmModels({ config: openAiConfig, fetchImpl: async (input, init) => { requests.push(new Request(input, init)); - return Response.json({ data: [{ id: "qwen2.5-coder" }] }); + return Response.json({ data: [{ id: "qwen2.5-coder" }, { id: "llama3.2" }] }); }, }); @@ -88,6 +88,20 @@ describe("listLlmModels", () => { assert.equal(result.degraded, true); }); + it("uses an unadvertised fallback alias instead of unrelated provider models", async () => { + const result = await listLlmModels({ + config: { ...openAiConfig, model: "uncensored" }, + fetchImpl: async () => + Response.json({ + data: [{ id: "auto" }, { id: "qwen3:8b" }, { id: "qwen3-coder:30b" }], + }), + }); + + assert.deepEqual(result.models, [{ id: "uncensored", label: "uncensored" }]); + assert.equal(result.fallbackModel, "uncensored"); + assert.equal(result.degraded, false); + }); + it("uses fallback only for mock provider config", async () => { const result = await listLlmModels({ config: { provider: "mock", baseUrl: null, apiKey: null, model: "llama3.2" },