Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 24 additions & 18 deletions desktop/src/features/mesh-compute/classifyModelRef.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -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",
});
});

Expand Down
19 changes: 12 additions & 7 deletions desktop/src/features/mesh-compute/classifyModelRef.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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`.
Expand All @@ -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 };
}
Original file line number Diff line number Diff line change
Expand Up @@ -301,8 +301,9 @@ export function MeshComputeSettingsCard() {
value={modelInput}
/>
<p className="text-sm font-normal text-muted-foreground">
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.
</p>
{catalog && catalog.entries.length > 0 ? (
<CatalogPicker
Expand Down
2 changes: 1 addition & 1 deletion desktop/tests/e2e/mesh-compute.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ test("Share compute selects the curated default and starts and stops sharing", a

await expect(card).toContainText("Not sharing right now");
await expect(card).toContainText(
"Choose a suggested model below, or enter a model reference or local file",
"Choose a suggested model below, or enter a catalog name or HuggingFace ref",
);
await expect(model).toHaveValue("Gemma-4-E4B-it-Q4_K_M");
await expect(toggle).toBeEnabled();
Expand Down