diff --git a/CHANGELOG.md b/CHANGELOG.md index af53a35..285eeb4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -36,6 +36,11 @@ APIs change between minors without a deprecation period. `ctl model plan` and `ctl model serve` take those repositories too, and the explorer names a `strata-ple` repository for what it is: part of a pack Strata knows, or one Strata 0.2.7 does not. +### Fixed + +- **A repository that keeps each quantisation in a directory of its own** (ISTA-DASLab's `IQ2_XS/`, `IQ3_S/`) listed + no weights: its file tree is now read recursively, by the explorer, the plan and `ctl model serve`. + ## v0.16.0 — 2026-10-30 Weights pinned to the commit they were added at and checked, by the nodes and the mirror, against the sha256 diff --git a/internal/hf/hf.go b/internal/hf/hf.go index a9c42ad..29dd7eb 100644 --- a/internal/hf/hf.go +++ b/internal/hf/hf.go @@ -469,7 +469,9 @@ func (c *Client) RepoAt(ctx context.Context, id, rev string) (Repo, error) { if rev == "" { rev = "main" } - if err := c.get(ctx, "/api/models/"+id+"/tree/"+rev, &tree); err != nil { + // Recursive: a repository that keeps each quantisation in a directory of + // its own — ISTA-DASLab's IQ2_XS/, IQ3_S/ — listed no weights at all. + if err := c.get(ctx, "/api/models/"+id+"/tree/"+rev+"?recursive=true", &tree); err != nil { return Repo{}, err } diff --git a/internal/hf/hf_test.go b/internal/hf/hf_test.go index bbfa67b..b626de9 100644 --- a/internal/hf/hf_test.go +++ b/internal/hf/hf_test.go @@ -266,3 +266,29 @@ func TestAListingNamesTheBaseModelAndLicenseFromItsTags(t *testing.T) { t.Error("a base model with no relation should read as itself") } } + +// A repository that keeps each quantisation in a directory of its own lists +// its weights: the tree is asked for recursively. +func TestFilesInDirectoriesAreListed(t *testing.T) { + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + switch { + case strings.Contains(r.URL.Path, "/tree/"): + if r.URL.Query().Get("recursive") != "true" { + _, _ = w.Write([]byte(`[{"type":"directory","path":"IQ2_XS"}]`)) + return + } + _, _ = w.Write([]byte(`[{"type":"directory","path":"IQ2_XS"}, + {"type":"file","path":"IQ2_XS/m-IQ2_XS-00001-of-00002.gguf","size":100,"lfs":{"oid":"aa","size":5000}}]`)) + default: + _, _ = w.Write([]byte(`{"id":"o/r","sha":"abc"}`)) + } + })) + defer srv.Close() + r, err := New(srv.URL, 0).Repo(t.Context(), "o/r") + if err != nil { + t.Fatal(err) + } + if len(r.Files) != 1 || r.Files[0].Path != "IQ2_XS/m-IQ2_XS-00001-of-00002.gguf" || r.Files[0].SHA256 != "aa" { + t.Errorf("%+v", r.Files) + } +}