diff --git a/open-sse/providers/capabilities.js b/open-sse/providers/capabilities.js index 79aee774..5b52516d 100644 --- a/open-sse/providers/capabilities.js +++ b/open-sse/providers/capabilities.js @@ -130,7 +130,10 @@ export const MODEL_CAPABILITIES = { // DeepSeek API; it previously fell through to the generic *deepseek* pattern, whose // 128K/64K limits are kept here. The repeated fields are deliberate: an exact entry // short-circuits the pattern table, so a vision-only delta would drop them. + // Some providers (e.g. Kenari) expose this model under the hyphenated ID + // "deepseek-v4-1-flash" (dash instead of dot); add it as an alias (#4293). "deepseek-v4.1-flash": { vision: true, reasoning: true, thinkingFormat: "deepseek", contextWindow: 1000000, maxOutput: 384000 }, + "deepseek-v4-1-flash": { vision: true, reasoning: true, thinkingFormat: "deepseek", contextWindow: 1000000, maxOutput: 384000 }, "deepseek-flash": { vision: true, reasoning: true, thinkingFormat: "deepseek", contextWindow: 128000, maxOutput: 64000 }, // Qwen plain coder/text (no vision) — registry "vision-model" / "coder-model" aliases diff --git a/src/shared/components/ModelSelectModal.js b/src/shared/components/ModelSelectModal.js index 20e17d95..af07f028 100644 --- a/src/shared/components/ModelSelectModal.js +++ b/src/shared/components/ModelSelectModal.js @@ -22,7 +22,9 @@ const NO_AUTH_PROVIDER_IDS = Object.keys(FREE_PROVIDERS).filter(id => FREE_PROVI // Providers with per-account live catalogs via /api/providers/[id]/models. // Static registry stays as fallback when live fetch fails or is empty. -const LIVE_CATALOG_PROVIDERS = ["cursor", "cline", "clinepass"]; +// zed added in #4244: its backend customResolver already returns live models +// but the frontend omitted it, hiding Zed entirely from the Combo picker. +const LIVE_CATALOG_PROVIDERS = ["cursor", "cline", "clinepass", "zed"]; // Fetch a provider's account-scoped catalog for every active connection and merge // the results. Entries collapse by model id on purpose: two connections of the @@ -112,10 +114,12 @@ export default function ModelSelectModal({ const cursorConnectionIds = liveConnectionIdsByProvider.cursor; const clineConnectionIds = liveConnectionIdsByProvider.cline; const clinepassConnectionIds = liveConnectionIdsByProvider.clinepass; + const zedConnectionIds = liveConnectionIdsByProvider.zed; const cursorModels = useLiveProviderModels(isOpen, cursorConnectionIds, "Cursor"); const clineModels = useLiveProviderModels(isOpen, clineConnectionIds, "Cline"); const clinepassModels = useLiveProviderModels(isOpen, clinepassConnectionIds, "ClinePass"); + const zedModels = useLiveProviderModels(isOpen, zedConnectionIds, "Zed"); const fetchCombos = async () => { try { @@ -348,7 +352,7 @@ export default function ModelSelectModal({ hasModels: mergedModels.length > 0, }; } else { - const liveModels = providerId === "cursor" ? cursorModels : providerId === "cline" ? clineModels : providerId === "clinepass" ? clinepassModels : []; + const liveModels = providerId === "cursor" ? cursorModels : providerId === "cline" ? clineModels : providerId === "clinepass" ? clinepassModels : providerId === "zed" ? zedModels : []; const hardcodedModels = liveModels.length > 0 ? liveModels : getModelsByProviderId(providerId); diff --git a/tests/unit/kenari-deepseek-vision-zed-modal.test.js b/tests/unit/kenari-deepseek-vision-zed-modal.test.js new file mode 100644 index 00000000..af65b8f3 --- /dev/null +++ b/tests/unit/kenari-deepseek-vision-zed-modal.test.js @@ -0,0 +1,80 @@ +/** + * Tests for two independent frontend/capabilities fixes: + * + * #4293 — deepseek-v4-1-flash (hyphen) has vision=false + * Kenari exposes the model under the ID "deepseek-v4-1-flash" (dash instead + * of dot). The capabilities table only had "deepseek-v4.1-flash" (dot), so + * the hyphenated variant fell through to the generic *deepseek* pattern which + * has vision:false. Fix: add "deepseek-v4-1-flash" as an alias with the same + * vision:true entry. + * + * #4244 — Zed missing from SelectModelModal LIVE_CATALOG_PROVIDERS + * The hardcoded list ["cursor","cline","clinepass"] omitted "zed", so the + * Zed provider card was never fetched and never shown in the Combo picker. + * Fix: add "zed" to the list (and wire up the corresponding state/hook/ternary). + */ + +import { describe, it, expect, beforeAll } from "vitest"; +import { getCapabilitiesForModel } from "../../open-sse/providers/capabilities.js"; + +// ── #4293 deepseek-v4-1-flash vision capability ─────────────────────────── + +describe("deepseek-v4-1-flash capabilities (#4293)", () => { + it("reports vision:true for the hyphenated deepseek-v4-1-flash id (Kenari variant)", () => { + // Any provider that uses the hyphenated id should get vision:true + const caps = getCapabilitiesForModel("kenari", "deepseek-v4-1-flash"); + expect(caps.vision).toBe(true); + }); + + it("still reports vision:true for the dotted deepseek-v4.1-flash id", () => { + const caps = getCapabilitiesForModel("ollama", "deepseek-v4.1-flash"); + expect(caps.vision).toBe(true); + }); + + it("still reports reasoning:true for deepseek-v4-1-flash", () => { + const caps = getCapabilitiesForModel("kenari", "deepseek-v4-1-flash"); + expect(caps.reasoning).toBe(true); + }); + + it("reports the correct contextWindow (1M) for deepseek-v4-1-flash", () => { + const caps = getCapabilitiesForModel("kenari", "deepseek-v4-1-flash"); + expect(caps.contextWindow).toBe(1000000); + }); +}); + +// ── #4244 LIVE_CATALOG_PROVIDERS includes zed ──────────────────────────── +// ModelSelectModal.js is JSX so we cannot import it in Vitest without a +// JSX transform. Read the source text and verify the constant definition +// directly — this is reliable and does not require a full React setup. + +import fs from "fs"; +import path from "path"; + +describe("ModelSelectModal LIVE_CATALOG_PROVIDERS includes zed (#4244)", () => { + let src; + beforeAll(() => { + const fileUrl = new URL("../../src/shared/components/ModelSelectModal.js", import.meta.url); + src = fs.readFileSync(fileUrl, "utf-8"); + }); + + it("includes zed in LIVE_CATALOG_PROVIDERS", () => { + // Match the const definition line and verify zed is present + const match = src.match(/const LIVE_CATALOG_PROVIDERS\s*=\s*\[([^\]]+)\]/); + expect(match).toBeTruthy(); + const list = match[1]; + expect(list).toContain('"zed"'); + }); + + it("still includes cursor, cline, clinepass", () => { + const match = src.match(/const LIVE_CATALOG_PROVIDERS\s*=\s*\[([^\]]+)\]/); + const list = match[1]; + expect(list).toContain('"cursor"'); + expect(list).toContain('"cline"'); + expect(list).toContain('"clinepass"'); + }); + + it("zedModels hook call is present in the file", () => { + expect(src).toContain("zedModels"); + expect(src).toContain("zedConnectionIds"); + }); +}); \ No newline at end of file