From f62831d105d247b67bb2ee8252a98e72be06af24 Mon Sep 17 00:00:00 2001 From: Sasha Mitchell Date: Sun, 27 Sep 2026 20:45:05 +0700 Subject: [PATCH] Cut binary GGUF part URLs at the matched suffix. A path like models/old.gguf/new.gguf.part1of2 was expanded from the first .gguf, so the download URLs dropped new.gguf. --- .../utils/resolveBinarySplitGgufPartUrls.ts | 13 ++++++++----- test/standalone/utils/modelDownloader.test.ts | 19 ++++++++++++++++++- 2 files changed, 26 insertions(+), 6 deletions(-) diff --git a/src/gguf/utils/resolveBinarySplitGgufPartUrls.ts b/src/gguf/utils/resolveBinarySplitGgufPartUrls.ts index 64736590..3ae70d1f 100644 --- a/src/gguf/utils/resolveBinarySplitGgufPartUrls.ts +++ b/src/gguf/utils/resolveBinarySplitGgufPartUrls.ts @@ -16,8 +16,11 @@ export function resolveBinarySplitGgufPartUrls(ggufUrl: string) { ) return ggufUrl; - const ggufIndex = parsedGgufUrl.pathname.indexOf(".gguf"); - const pathnameWithoutPart = parsedGgufUrl.pathname.slice(0, ggufIndex + ".gguf".length); + // The directory can contain ".gguf" too. Cut at this match, not the first one. + if (binaryPartsMatch.index == null) + return ggufUrl; + + const pathnameWithoutPart = parsedGgufUrl.pathname.slice(0, binaryPartsMatch.index + ".gguf".length); const res: string[] = []; for (let i = 1; i <= parts; i++) { @@ -38,9 +41,9 @@ export function getFilenameForBinarySplitGgufPartUrls(urls: string[]) { const firstParsedUrl = new URL(urls[0]!); - if (binarySplitGgufPartsRegex.test(firstParsedUrl.pathname)) { - const ggufIndex = firstParsedUrl.pathname.toLowerCase().indexOf(".gguf"); - const urlWithoutPart = firstParsedUrl.pathname.slice(0, ggufIndex + ".gguf".length); + const binaryPartsMatch = firstParsedUrl.pathname.match(binarySplitGgufPartsRegex); + if (binaryPartsMatch?.index != null) { + const urlWithoutPart = firstParsedUrl.pathname.slice(0, binaryPartsMatch.index + ".gguf".length); const filename = decodeURIComponent(urlWithoutPart.split("/").pop()!); return filenamify(filename); diff --git a/test/standalone/utils/modelDownloader.test.ts b/test/standalone/utils/modelDownloader.test.ts index feba028e..7b033e8f 100644 --- a/test/standalone/utils/modelDownloader.test.ts +++ b/test/standalone/utils/modelDownloader.test.ts @@ -1,5 +1,5 @@ import {describe, expect, test} from "vitest"; -import {getFilenameForBinarySplitGgufPartUrls} from "../../../src/gguf/utils/resolveBinarySplitGgufPartUrls.js"; +import {getFilenameForBinarySplitGgufPartUrls, resolveBinarySplitGgufPartUrls} from "../../../src/gguf/utils/resolveBinarySplitGgufPartUrls.js"; describe("utils", () => { @@ -19,5 +19,22 @@ describe("utils", () => { ]); expect(res).to.eql("model.Q6_K.gguf"); }); + + test("keeps an earlier .gguf directory when expanding part urls", async () => { + const res = resolveBinarySplitGgufPartUrls("https://example.com/models/old.gguf/new.gguf.part1of2?hi=true"); + + expect(res).to.eql([ + "https://example.com/models/old.gguf/new.gguf.part1of2?hi=true", + "https://example.com/models/old.gguf/new.gguf.part2of2?hi=true" + ]); + }); + + test("filename ignores an earlier .gguf directory", async () => { + const res = getFilenameForBinarySplitGgufPartUrls([ + "https://example.com/models/old.gguf/new.gguf.part1of2" + ]); + + expect(res).to.eql("new.gguf"); + }); }); });