diff --git a/desktop/src/features/mesh-compute/classifyModelRef.test.mjs b/desktop/src/features/mesh-compute/classifyModelRef.test.mjs index 9876678c8a..448888def0 100644 --- a/desktop/src/features/mesh-compute/classifyModelRef.test.mjs +++ b/desktop/src/features/mesh-compute/classifyModelRef.test.mjs @@ -15,32 +15,38 @@ test("hf:// prefix → huggingface", () => { }); }); -test("absolute path → local-path", () => { +// The vendored mesh-llm SDK's `parse_exact_model_ref` has no local-path +// variant — feeding any of these into `mesh_start_node` always fails with +// "Expected an exact model ref" at runtime. We therefore classify them as +// unknown so the UI keeps the Start button disabled with a clear signal +// instead of letting the submit bubble up a cryptic SDK error. +// See https://github.com/block/buzz/issues/4049. + +test("absolute path → unknown", () => { assert.deepEqual(classifyModelRef("/Users/me/models/qwen.gguf"), { - kind: "local-path", - path: "/Users/me/models/qwen.gguf", + kind: "unknown", }); }); -test("relative path with ./ → local-path", () => { - assert.deepEqual(classifyModelRef("./models/qwen.gguf"), { - kind: "local-path", - path: "./models/qwen.gguf", - }); +test("relative path with ./ → unknown", () => { + assert.deepEqual(classifyModelRef("./models/qwen.gguf"), { kind: "unknown" }); }); -test("home shortcut → local-path", () => { - assert.deepEqual(classifyModelRef("~/models/qwen.gguf"), { - kind: "local-path", - path: "~/models/qwen.gguf", - }); +test("relative path with ../ → unknown", () => { + assert.deepEqual(classifyModelRef("../models/qwen.gguf"), { kind: "unknown" }); +}); + +test("home shortcut → unknown", () => { + assert.deepEqual(classifyModelRef("~/models/qwen.gguf"), { kind: "unknown" }); +}); + +test(".gguf extension without path prefix → unknown", () => { + assert.deepEqual(classifyModelRef("my-model.gguf"), { kind: "unknown" }); }); -test(".gguf extension without path prefix → local-path", () => { - // Bare filename ending in .gguf — user clearly means a file. - assert.deepEqual(classifyModelRef("my-model.gguf"), { - kind: "local-path", - path: "my-model.gguf", +test("file:// URL → unknown", () => { + assert.deepEqual(classifyModelRef("file:/// Users/me/models/qwen.gguf"), { + kind: "unknown", }); }); diff --git a/desktop/src/features/mesh-compute/classifyModelRef.ts b/desktop/src/features/mesh-compute/classifyModelRef.ts index 666c56b309..84aee6865e 100644 --- a/desktop/src/features/mesh-compute/classifyModelRef.ts +++ b/desktop/src/features/mesh-compute/classifyModelRef.ts @@ -5,17 +5,21 @@ export type ModelRefKind = | { kind: "catalog"; name: string } | { kind: "huggingface"; ref: string } - | { kind: "local-path"; path: string } | { kind: "unknown" }; /** * Classify a model-ref string the way mesh-llm's runtime does: * - `hf://…` → HuggingFace ref - * - starts with `/` or `./` or `~`, OR ends with `.gguf` → local file * - otherwise non-empty → catalog name * - empty/whitespace → unknown * - * Source: mesh runtime/mod.rs:3390 ("local file, catalog name, or HuggingFace URL"). + * Local filesystem paths are classified `unknown`, NOT `local-path`: the + * vendored mesh-llm SDK's `parse_exact_model_ref` has no local-path variant, + * so feeding a path into `mesh_start_node` always fails with "Expected an + * exact model ref". Treating paths as unknown keeps the Start button + * disabled with a clear UI-level signal instead of a runtime error at start. + * Reclassify-and-accept if/when the SDK gains a local-path branch. + * See https://github.com/block/buzz/issues/4049. * * This is validation-only — canonical resolution happens server-side via * `mesh_start_node`. @@ -28,16 +32,17 @@ export function classifyModelRef(raw: string): ModelRefKind { if (trimmed.startsWith("hf://")) { return { kind: "huggingface", ref: trimmed }; } - // Local path heuristics. Conservative: only mark as path when there are - // unambiguous signals (leading separator, home shortcut, .gguf extension). + // Local path heuristics — classify as unknown so the form refuses to + // submit. See the function-level doc-comment above and issue #4049. const looksLikePath = trimmed.startsWith("/") || trimmed.startsWith("./") || trimmed.startsWith("../") || trimmed.startsWith("~") || - trimmed.toLowerCase().endsWith(".gguf"); + trimmed.toLowerCase().endsWith(".gguf") || + trimmed.startsWith("file://"); if (looksLikePath) { - return { kind: "local-path", path: trimmed }; + return { kind: "unknown" }; } return { kind: "catalog", name: trimmed }; } diff --git a/desktop/src/features/mesh-compute/ui/MeshComputeSettingsCard.tsx b/desktop/src/features/mesh-compute/ui/MeshComputeSettingsCard.tsx index fd7550eff0..ef7552a986 100644 --- a/desktop/src/features/mesh-compute/ui/MeshComputeSettingsCard.tsx +++ b/desktop/src/features/mesh-compute/ui/MeshComputeSettingsCard.tsx @@ -301,8 +301,9 @@ export function MeshComputeSettingsCard() { value={modelInput} />
- Choose a suggested model below, or enter a model reference or - local file. Buzz downloads remote models when sharing starts. + Choose a suggested model below, or enter a catalog name or + HuggingFace ref. Buzz downloads remote models when sharing + starts; local files are not supported yet.
{catalog && catalog.entries.length > 0 ? (