@infersec/conduit 1.6.0 → 1.6.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +2 -2
- package/dist/index.js +2 -2
- package/dist/{start-XRPU7RSB.js → start-CQayYNYZ.js} +399 -51
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -6,7 +6,7 @@ const __dirname = __pathDirname(__filename);
|
|
|
6
6
|
|
|
7
7
|
import { parseArgs } from 'node:util';
|
|
8
8
|
import 'node:crypto';
|
|
9
|
-
import { a as asError, s as startInferenceAgent } from './start-
|
|
9
|
+
import { a as asError, s as startInferenceAgent } from './start-CQayYNYZ.js';
|
|
10
10
|
import 'argon2';
|
|
11
11
|
import 'node:child_process';
|
|
12
12
|
import 'node:stream';
|
|
@@ -45,8 +45,8 @@ import 'querystring';
|
|
|
45
45
|
import 'url';
|
|
46
46
|
import 'http';
|
|
47
47
|
import 'crypto';
|
|
48
|
-
import 'node:stream/promises';
|
|
49
48
|
import 'node:fs';
|
|
49
|
+
import 'node:stream/promises';
|
|
50
50
|
import 'fs/promises';
|
|
51
51
|
import 'stream/promises';
|
|
52
52
|
import 'node:string_decoder';
|
package/dist/index.js
CHANGED
|
@@ -5,7 +5,7 @@ const __filename = __fileURLToPath(import.meta.url);
|
|
|
5
5
|
const __dirname = __pathDirname(__filename);
|
|
6
6
|
|
|
7
7
|
import 'node:crypto';
|
|
8
|
-
import { s as startInferenceAgent, a as asError } from './start-
|
|
8
|
+
import { s as startInferenceAgent, a as asError } from './start-CQayYNYZ.js';
|
|
9
9
|
import 'argon2';
|
|
10
10
|
import 'node:child_process';
|
|
11
11
|
import 'node:stream';
|
|
@@ -45,8 +45,8 @@ import 'querystring';
|
|
|
45
45
|
import 'url';
|
|
46
46
|
import 'http';
|
|
47
47
|
import 'crypto';
|
|
48
|
-
import 'node:stream/promises';
|
|
49
48
|
import 'node:fs';
|
|
49
|
+
import 'node:stream/promises';
|
|
50
50
|
import 'fs/promises';
|
|
51
51
|
import 'stream/promises';
|
|
52
52
|
import 'node:string_decoder';
|
|
@@ -30,8 +30,8 @@ import require$$1$3 from 'node:worker_threads';
|
|
|
30
30
|
import require$$1$5, { fileURLToPath } from 'node:url';
|
|
31
31
|
import require$$1$6 from 'node:async_hooks';
|
|
32
32
|
import require$$1$7 from 'node:console';
|
|
33
|
-
import require$$0$b, { mkdir, readFile, writeFile, realpath, readlink, readdir, lstat } from 'node:fs/promises';
|
|
34
|
-
import path$1, {
|
|
33
|
+
import require$$0$b, { mkdir, readFile, writeFile, stat, unlink, rename, realpath, readlink, readdir, lstat } from 'node:fs/promises';
|
|
34
|
+
import path$1, { join, dirname, win32, posix } from 'node:path';
|
|
35
35
|
import require$$1$8 from 'node:dns';
|
|
36
36
|
import require$$2$4 from 'node:sqlite';
|
|
37
37
|
import require$$0$c from 'path';
|
|
@@ -44,9 +44,9 @@ import require$$8$1 from 'querystring';
|
|
|
44
44
|
import require$$0$f from 'url';
|
|
45
45
|
import require$$0$g from 'http';
|
|
46
46
|
import require$$0$h from 'crypto';
|
|
47
|
-
import { pipeline, finished } from 'node:stream/promises';
|
|
48
47
|
import * as actualFS from 'node:fs';
|
|
49
48
|
import { existsSync, createWriteStream, statSync, readFileSync, appendFileSync, writeFileSync, createReadStream } from 'node:fs';
|
|
49
|
+
import { pipeline, finished } from 'node:stream/promises';
|
|
50
50
|
import 'fs/promises';
|
|
51
51
|
import 'stream/promises';
|
|
52
52
|
import { StringDecoder } from 'node:string_decoder';
|
|
@@ -96473,11 +96473,13 @@ const ModelDownloadProgressSchema = object({
|
|
|
96473
96473
|
completedFiles: array(string$1().min(1))
|
|
96474
96474
|
});
|
|
96475
96475
|
|
|
96476
|
-
const DOWNLOAD_PROGRESS_TIMEOUT =
|
|
96477
|
-
const
|
|
96476
|
+
const DOWNLOAD_PROGRESS_TIMEOUT = 60000;
|
|
96477
|
+
const DOWNLOAD_RETRY_ATTEMPTS_FULL = 3;
|
|
96478
|
+
const DOWNLOAD_RETRY_ATTEMPTS_RANGE = 10;
|
|
96478
96479
|
async function downloadModelViaHuggingFace({ format, huggingFaceToken, modelSlug: rawModelSlug, progressFilePath, targetDirectory }) {
|
|
96479
96480
|
// Sanitise model ID
|
|
96480
|
-
const [
|
|
96481
|
+
const [modelSlugWithRevision, variant = null] = rawModelSlug.split(":");
|
|
96482
|
+
const { modelSlug, revision } = parseModelRevision(modelSlugWithRevision);
|
|
96481
96483
|
// Prepare directory
|
|
96482
96484
|
await mkdir(targetDirectory, { recursive: true });
|
|
96483
96485
|
let progress;
|
|
@@ -96512,65 +96514,411 @@ async function downloadModelViaHuggingFace({ format, huggingFaceToken, modelSlug
|
|
|
96512
96514
|
console.log("Skipping due to already having completed file:", file.path, file.size);
|
|
96513
96515
|
continue;
|
|
96514
96516
|
}
|
|
96515
|
-
|
|
96516
|
-
|
|
96517
|
-
|
|
96517
|
+
const rangeInfo = await getRangeInfo({
|
|
96518
|
+
accessToken,
|
|
96519
|
+
filePath: file.path,
|
|
96520
|
+
fileSize: file.size,
|
|
96521
|
+
modelSlug,
|
|
96522
|
+
revision
|
|
96523
|
+
});
|
|
96524
|
+
const { partialPath } = getDownloadPaths({ filePath: file.path, targetDirectory });
|
|
96525
|
+
const shouldAttemptRange = rangeInfo.supportsRanges || existsSync(partialPath);
|
|
96526
|
+
if (shouldAttemptRange) {
|
|
96518
96527
|
try {
|
|
96519
|
-
|
|
96520
|
-
await mkdir(dirname(join(targetDirectory, file.path)), { recursive: true });
|
|
96521
|
-
const response = await downloadFile({
|
|
96528
|
+
await downloadFileWithRange({
|
|
96522
96529
|
accessToken,
|
|
96523
|
-
|
|
96524
|
-
|
|
96525
|
-
|
|
96530
|
+
filePath: file.path,
|
|
96531
|
+
fileSize: rangeInfo.totalSize ?? file.size,
|
|
96532
|
+
modelSlug,
|
|
96533
|
+
revision,
|
|
96534
|
+
targetDirectory
|
|
96526
96535
|
});
|
|
96527
|
-
if (!response) {
|
|
96528
|
-
throw new Error(`Requested file did not return a valid response: ${file.path}`);
|
|
96529
|
-
}
|
|
96530
|
-
const input = Readable.fromWeb(response.stream());
|
|
96531
|
-
const meter = watchStreamProgress(25 * 1024 * 1024); // 25 MiB intervals
|
|
96532
|
-
const output = createWriteStream(join(targetDirectory, file.path));
|
|
96533
|
-
let progressTimeout = null;
|
|
96534
|
-
let lastPercentage = "0.0";
|
|
96535
|
-
let lastProgressBytes = 0;
|
|
96536
|
-
meter.progress.on("progress", (totalBytes) => {
|
|
96537
|
-
lastProgressBytes = totalBytes;
|
|
96538
|
-
const percentComplete = ((totalBytes / file.size) * 100).toFixed(1);
|
|
96539
|
-
if (lastPercentage !== percentComplete) {
|
|
96540
|
-
console.log(` => ${percentComplete}% (${totalBytes} / ${file.size})`);
|
|
96541
|
-
}
|
|
96542
|
-
lastPercentage = percentComplete;
|
|
96543
|
-
clearTimeout(progressTimeout);
|
|
96544
|
-
progressTimeout = setTimeout(() => {
|
|
96545
|
-
input.destroy(new Error(`Timed out with no progress for ${DOWNLOAD_PROGRESS_TIMEOUT}ms while downloading ${file.path}. Last progress: ${lastPercentage}% (${lastProgressBytes} / ${file.size})`));
|
|
96546
|
-
}, DOWNLOAD_PROGRESS_TIMEOUT);
|
|
96547
|
-
});
|
|
96548
|
-
try {
|
|
96549
|
-
await pipeline(input, meter, output);
|
|
96550
|
-
}
|
|
96551
|
-
finally {
|
|
96552
|
-
clearTimeout(progressTimeout);
|
|
96553
|
-
}
|
|
96554
|
-
downloaded = true;
|
|
96555
|
-
break;
|
|
96556
96536
|
}
|
|
96557
96537
|
catch (error) {
|
|
96558
|
-
|
|
96559
|
-
|
|
96560
|
-
|
|
96561
|
-
|
|
96538
|
+
if (error instanceof RangeNotSupportedError) {
|
|
96539
|
+
console.warn(`Range download unavailable for ${file.path}: ${error.message}`);
|
|
96540
|
+
await downloadFileFull({
|
|
96541
|
+
accessToken,
|
|
96542
|
+
filePath: file.path,
|
|
96543
|
+
fileSize: file.size,
|
|
96544
|
+
modelSlug,
|
|
96545
|
+
targetDirectory
|
|
96546
|
+
});
|
|
96547
|
+
}
|
|
96548
|
+
else {
|
|
96549
|
+
throw error;
|
|
96562
96550
|
}
|
|
96563
96551
|
}
|
|
96564
96552
|
}
|
|
96565
|
-
|
|
96566
|
-
|
|
96567
|
-
|
|
96553
|
+
else {
|
|
96554
|
+
if (rangeInfo.reason) {
|
|
96555
|
+
console.warn(`Range download unavailable for ${file.path}: ${rangeInfo.reason}`);
|
|
96556
|
+
}
|
|
96557
|
+
await downloadFileFull({
|
|
96558
|
+
accessToken,
|
|
96559
|
+
filePath: file.path,
|
|
96560
|
+
fileSize: file.size,
|
|
96561
|
+
modelSlug,
|
|
96562
|
+
targetDirectory
|
|
96563
|
+
});
|
|
96568
96564
|
}
|
|
96569
96565
|
// Update progress
|
|
96570
96566
|
progress.completedFiles.push(file.path);
|
|
96571
96567
|
await writeFile(progressFilePath, JSON.stringify(progress, undefined, 4));
|
|
96572
96568
|
}
|
|
96573
96569
|
}
|
|
96570
|
+
function encodePathSegments(path) {
|
|
96571
|
+
return path
|
|
96572
|
+
.split("/")
|
|
96573
|
+
.map(segment => encodeURIComponent(segment))
|
|
96574
|
+
.join("/");
|
|
96575
|
+
}
|
|
96576
|
+
function encodeRepoSlug(modelSlug) {
|
|
96577
|
+
return modelSlug
|
|
96578
|
+
.split("/")
|
|
96579
|
+
.map(segment => encodeURIComponent(segment))
|
|
96580
|
+
.join("/");
|
|
96581
|
+
}
|
|
96582
|
+
function parseModelRevision(modelSlugWithRevision) {
|
|
96583
|
+
const revisionIndex = modelSlugWithRevision.indexOf("@");
|
|
96584
|
+
if (revisionIndex === -1) {
|
|
96585
|
+
return { modelSlug: modelSlugWithRevision, revision: "main" };
|
|
96586
|
+
}
|
|
96587
|
+
return {
|
|
96588
|
+
modelSlug: modelSlugWithRevision.slice(0, revisionIndex),
|
|
96589
|
+
revision: modelSlugWithRevision.slice(revisionIndex + 1)
|
|
96590
|
+
};
|
|
96591
|
+
}
|
|
96592
|
+
function getDownloadPaths({ filePath, targetDirectory }) {
|
|
96593
|
+
const finalPath = join(targetDirectory, filePath);
|
|
96594
|
+
return {
|
|
96595
|
+
finalPath,
|
|
96596
|
+
partialPath: `${finalPath}.partial`
|
|
96597
|
+
};
|
|
96598
|
+
}
|
|
96599
|
+
function getResolveURL({ filePath, modelSlug, revision }) {
|
|
96600
|
+
const encodedRepo = encodeRepoSlug(modelSlug);
|
|
96601
|
+
const encodedRevision = encodeURIComponent(revision);
|
|
96602
|
+
const encodedPath = encodePathSegments(filePath);
|
|
96603
|
+
return `https://huggingface.co/${encodedRepo}/resolve/${encodedRevision}/${encodedPath}`;
|
|
96604
|
+
}
|
|
96605
|
+
function getResolveDownloadURL({ filePath, modelSlug, revision }) {
|
|
96606
|
+
const baseUrl = getResolveURL({ filePath, modelSlug, revision });
|
|
96607
|
+
const resolvedUrl = new URL(baseUrl);
|
|
96608
|
+
resolvedUrl.searchParams.set("download", "1");
|
|
96609
|
+
return resolvedUrl.toString();
|
|
96610
|
+
}
|
|
96611
|
+
class RangeNotSupportedError extends Error {
|
|
96612
|
+
constructor(message) {
|
|
96613
|
+
super(message);
|
|
96614
|
+
this.name = "RangeNotSupportedError";
|
|
96615
|
+
}
|
|
96616
|
+
}
|
|
96617
|
+
function getAuthHeaders(accessToken) {
|
|
96618
|
+
if (!accessToken) {
|
|
96619
|
+
return {};
|
|
96620
|
+
}
|
|
96621
|
+
return {
|
|
96622
|
+
Authorization: `Bearer ${accessToken}`
|
|
96623
|
+
};
|
|
96624
|
+
}
|
|
96625
|
+
function shouldSendAuthHeader(url) {
|
|
96626
|
+
try {
|
|
96627
|
+
const hostname = new URL(url).hostname.toLowerCase();
|
|
96628
|
+
return hostname.endsWith("huggingface.co") || hostname.endsWith("hf.co");
|
|
96629
|
+
}
|
|
96630
|
+
catch {
|
|
96631
|
+
return false;
|
|
96632
|
+
}
|
|
96633
|
+
}
|
|
96634
|
+
function parseContentRangeTotal(contentRange) {
|
|
96635
|
+
if (!contentRange) {
|
|
96636
|
+
return null;
|
|
96637
|
+
}
|
|
96638
|
+
const totalMatch = /\/(\d+)$/.exec(contentRange);
|
|
96639
|
+
if (!totalMatch) {
|
|
96640
|
+
return null;
|
|
96641
|
+
}
|
|
96642
|
+
const totalSize = Number(totalMatch[1]);
|
|
96643
|
+
return Number.isFinite(totalSize) ? totalSize : null;
|
|
96644
|
+
}
|
|
96645
|
+
async function getDownloadURL({ accessToken, filePath, modelSlug, revision }) {
|
|
96646
|
+
const url = getResolveDownloadURL({ filePath, modelSlug, revision });
|
|
96647
|
+
try {
|
|
96648
|
+
const response = await undiciExports.fetch(url, {
|
|
96649
|
+
method: "GET",
|
|
96650
|
+
redirect: "manual",
|
|
96651
|
+
headers: getAuthHeaders(accessToken)
|
|
96652
|
+
});
|
|
96653
|
+
try {
|
|
96654
|
+
if (response.status >= 300 && response.status < 400) {
|
|
96655
|
+
const location = response.headers.get("location");
|
|
96656
|
+
if (location) {
|
|
96657
|
+
return { reason: null, status: response.status, url: location };
|
|
96658
|
+
}
|
|
96659
|
+
}
|
|
96660
|
+
if (response.ok) {
|
|
96661
|
+
return { reason: null, status: response.status, url: response.url };
|
|
96662
|
+
}
|
|
96663
|
+
let responseBody = "<unavailable>";
|
|
96664
|
+
try {
|
|
96665
|
+
responseBody = await response.text();
|
|
96666
|
+
}
|
|
96667
|
+
catch {
|
|
96668
|
+
responseBody = "<failed to read body>";
|
|
96669
|
+
}
|
|
96670
|
+
if (responseBody.length > 2000) {
|
|
96671
|
+
responseBody = responseBody.slice(0, 2000);
|
|
96672
|
+
}
|
|
96673
|
+
console.warn(`Resolve request failed body for ${filePath}: ${responseBody}`);
|
|
96674
|
+
return {
|
|
96675
|
+
reason: `Resolve request failed with status ${response.status}`,
|
|
96676
|
+
status: response.status,
|
|
96677
|
+
url: null
|
|
96678
|
+
};
|
|
96679
|
+
}
|
|
96680
|
+
finally {
|
|
96681
|
+
if (response.body && response.bodyUsed === false && response.body.locked === false) {
|
|
96682
|
+
await response.body.cancel();
|
|
96683
|
+
}
|
|
96684
|
+
}
|
|
96685
|
+
}
|
|
96686
|
+
catch (error) {
|
|
96687
|
+
const parsed = asError(error);
|
|
96688
|
+
return {
|
|
96689
|
+
reason: `Resolve request failed: ${parsed.message}`,
|
|
96690
|
+
status: null,
|
|
96691
|
+
url: null
|
|
96692
|
+
};
|
|
96693
|
+
}
|
|
96694
|
+
}
|
|
96695
|
+
async function getRangeInfo({ accessToken, filePath, fileSize, modelSlug, revision }) {
|
|
96696
|
+
const downloadUrlInfo = await getDownloadURL({
|
|
96697
|
+
accessToken,
|
|
96698
|
+
filePath,
|
|
96699
|
+
modelSlug,
|
|
96700
|
+
revision
|
|
96701
|
+
});
|
|
96702
|
+
if (!downloadUrlInfo.url) {
|
|
96703
|
+
return {
|
|
96704
|
+
reason: downloadUrlInfo.reason ?? "Resolve request failed",
|
|
96705
|
+
supportsRanges: false,
|
|
96706
|
+
totalSize: fileSize
|
|
96707
|
+
};
|
|
96708
|
+
}
|
|
96709
|
+
try {
|
|
96710
|
+
const response = await undiciExports.fetch(downloadUrlInfo.url, {
|
|
96711
|
+
method: "GET",
|
|
96712
|
+
headers: {
|
|
96713
|
+
...(shouldSendAuthHeader(downloadUrlInfo.url) ? getAuthHeaders(accessToken) : {}),
|
|
96714
|
+
Range: "bytes=0-0"
|
|
96715
|
+
}
|
|
96716
|
+
});
|
|
96717
|
+
try {
|
|
96718
|
+
if (response.status === 206) {
|
|
96719
|
+
const totalSize = parseContentRangeTotal(response.headers.get("content-range"));
|
|
96720
|
+
return {
|
|
96721
|
+
reason: null,
|
|
96722
|
+
supportsRanges: true,
|
|
96723
|
+
totalSize: totalSize ?? fileSize
|
|
96724
|
+
};
|
|
96725
|
+
}
|
|
96726
|
+
if (response.status === 200) {
|
|
96727
|
+
return {
|
|
96728
|
+
reason: "Server returned 200 to range probe",
|
|
96729
|
+
supportsRanges: false,
|
|
96730
|
+
totalSize: fileSize
|
|
96731
|
+
};
|
|
96732
|
+
}
|
|
96733
|
+
return {
|
|
96734
|
+
reason: `Range probe failed with status ${response.status}`,
|
|
96735
|
+
supportsRanges: false,
|
|
96736
|
+
totalSize: fileSize
|
|
96737
|
+
};
|
|
96738
|
+
}
|
|
96739
|
+
finally {
|
|
96740
|
+
await response.body?.cancel();
|
|
96741
|
+
}
|
|
96742
|
+
}
|
|
96743
|
+
catch (error) {
|
|
96744
|
+
const parsed = asError(error);
|
|
96745
|
+
return {
|
|
96746
|
+
reason: `Range probe failed: ${parsed.message}`,
|
|
96747
|
+
supportsRanges: false,
|
|
96748
|
+
totalSize: fileSize
|
|
96749
|
+
};
|
|
96750
|
+
}
|
|
96751
|
+
}
|
|
96752
|
+
async function downloadFileFull({ accessToken, filePath, fileSize, modelSlug, targetDirectory }) {
|
|
96753
|
+
const { finalPath, partialPath } = getDownloadPaths({ filePath, targetDirectory });
|
|
96754
|
+
let lastError = null;
|
|
96755
|
+
for (let attempt = 1; attempt <= DOWNLOAD_RETRY_ATTEMPTS_FULL; attempt++) {
|
|
96756
|
+
try {
|
|
96757
|
+
console.log("Downloading:", filePath, fileSize, `(attempt ${attempt})`);
|
|
96758
|
+
await mkdir(dirname(finalPath), { recursive: true });
|
|
96759
|
+
if (existsSync(partialPath)) {
|
|
96760
|
+
await unlink(partialPath);
|
|
96761
|
+
}
|
|
96762
|
+
const response = await downloadFile({
|
|
96763
|
+
accessToken,
|
|
96764
|
+
repo: modelSlug,
|
|
96765
|
+
path: filePath
|
|
96766
|
+
});
|
|
96767
|
+
if (!response) {
|
|
96768
|
+
throw new Error(`Requested file did not return a valid response: ${filePath}`);
|
|
96769
|
+
}
|
|
96770
|
+
const input = Readable.fromWeb(response.stream());
|
|
96771
|
+
const meter = watchStreamProgress(25 * 1024 * 1024); // 25 MiB intervals
|
|
96772
|
+
const output = createWriteStream(partialPath, { flags: "w" });
|
|
96773
|
+
let progressTimeout = null;
|
|
96774
|
+
let lastPercentage = "0.0";
|
|
96775
|
+
let lastProgressBytes = 0;
|
|
96776
|
+
const resetProgressTimeout = () => {
|
|
96777
|
+
clearTimeout(progressTimeout);
|
|
96778
|
+
progressTimeout = setTimeout(() => {
|
|
96779
|
+
input.destroy(new Error(`Timed out with no progress for ${DOWNLOAD_PROGRESS_TIMEOUT}ms while downloading ${filePath}. Last progress: ${lastPercentage}% (${lastProgressBytes} / ${fileSize})`));
|
|
96780
|
+
}, DOWNLOAD_PROGRESS_TIMEOUT);
|
|
96781
|
+
};
|
|
96782
|
+
input.on("data", resetProgressTimeout);
|
|
96783
|
+
resetProgressTimeout();
|
|
96784
|
+
meter.progress.on("progress", (totalBytes) => {
|
|
96785
|
+
lastProgressBytes = totalBytes;
|
|
96786
|
+
const percentComplete = ((totalBytes / fileSize) * 100).toFixed(1);
|
|
96787
|
+
if (lastPercentage !== percentComplete) {
|
|
96788
|
+
console.log(` => ${percentComplete}% (${totalBytes} / ${fileSize})`);
|
|
96789
|
+
}
|
|
96790
|
+
lastPercentage = percentComplete;
|
|
96791
|
+
});
|
|
96792
|
+
try {
|
|
96793
|
+
await pipeline(input, meter, output);
|
|
96794
|
+
}
|
|
96795
|
+
finally {
|
|
96796
|
+
clearTimeout(progressTimeout);
|
|
96797
|
+
}
|
|
96798
|
+
await rename(partialPath, finalPath);
|
|
96799
|
+
return;
|
|
96800
|
+
}
|
|
96801
|
+
catch (error) {
|
|
96802
|
+
lastError = asError(error);
|
|
96803
|
+
if (attempt < DOWNLOAD_RETRY_ATTEMPTS_FULL) {
|
|
96804
|
+
console.warn(`Retrying full download (${attempt}/${DOWNLOAD_RETRY_ATTEMPTS_FULL}) for ${filePath} due to error: ${lastError.message}`);
|
|
96805
|
+
continue;
|
|
96806
|
+
}
|
|
96807
|
+
}
|
|
96808
|
+
}
|
|
96809
|
+
const errorMessage = `Failed downloading ${filePath} (${fileSize} bytes) after ${DOWNLOAD_RETRY_ATTEMPTS_FULL} attempts. Last error: ${lastError?.message ?? "Unknown error"}`;
|
|
96810
|
+
throw new Error(errorMessage);
|
|
96811
|
+
}
|
|
96812
|
+
async function downloadFileWithRange({ accessToken, filePath, fileSize, modelSlug, revision, targetDirectory }) {
|
|
96813
|
+
const { finalPath, partialPath } = getDownloadPaths({ filePath, targetDirectory });
|
|
96814
|
+
let lastError = null;
|
|
96815
|
+
for (let attempt = 1; attempt <= DOWNLOAD_RETRY_ATTEMPTS_RANGE; attempt++) {
|
|
96816
|
+
let startOffset = 0;
|
|
96817
|
+
if (existsSync(partialPath)) {
|
|
96818
|
+
const currentStats = await stat(partialPath);
|
|
96819
|
+
startOffset = currentStats.size;
|
|
96820
|
+
if (startOffset > fileSize) {
|
|
96821
|
+
await unlink(partialPath);
|
|
96822
|
+
startOffset = 0;
|
|
96823
|
+
}
|
|
96824
|
+
if (startOffset === fileSize) {
|
|
96825
|
+
await rename(partialPath, finalPath);
|
|
96826
|
+
return;
|
|
96827
|
+
}
|
|
96828
|
+
}
|
|
96829
|
+
try {
|
|
96830
|
+
const resumeLabel = startOffset > 0 ? `resuming at ${startOffset}` : "starting";
|
|
96831
|
+
console.log("Downloading:", filePath, fileSize, `(attempt ${attempt}, ${resumeLabel})`);
|
|
96832
|
+
await mkdir(dirname(finalPath), { recursive: true });
|
|
96833
|
+
const downloadUrlInfo = await getDownloadURL({
|
|
96834
|
+
accessToken,
|
|
96835
|
+
filePath,
|
|
96836
|
+
modelSlug,
|
|
96837
|
+
revision
|
|
96838
|
+
});
|
|
96839
|
+
if (!downloadUrlInfo.url) {
|
|
96840
|
+
if (downloadUrlInfo.status === 400) {
|
|
96841
|
+
throw new RangeNotSupportedError(downloadUrlInfo.reason ?? "Resolve request failed");
|
|
96842
|
+
}
|
|
96843
|
+
throw new Error(downloadUrlInfo.reason ?? "Resolve request failed");
|
|
96844
|
+
}
|
|
96845
|
+
const response = await undiciExports.fetch(downloadUrlInfo.url, {
|
|
96846
|
+
headers: {
|
|
96847
|
+
...(shouldSendAuthHeader(downloadUrlInfo.url)
|
|
96848
|
+
? getAuthHeaders(accessToken)
|
|
96849
|
+
: {}),
|
|
96850
|
+
Range: `bytes=${startOffset}-`
|
|
96851
|
+
}
|
|
96852
|
+
});
|
|
96853
|
+
if (!response.ok) {
|
|
96854
|
+
let responseBody = "<unavailable>";
|
|
96855
|
+
try {
|
|
96856
|
+
responseBody = await response.text();
|
|
96857
|
+
}
|
|
96858
|
+
catch {
|
|
96859
|
+
responseBody = "<failed to read body>";
|
|
96860
|
+
}
|
|
96861
|
+
if (responseBody.length > 2000) {
|
|
96862
|
+
responseBody = responseBody.slice(0, 2000);
|
|
96863
|
+
}
|
|
96864
|
+
console.warn(`Range request failed response body for ${filePath}: ${responseBody}`);
|
|
96865
|
+
throw new Error(`Range request failed with status ${response.status} for ${filePath}`);
|
|
96866
|
+
}
|
|
96867
|
+
if (startOffset > 0 && response.status !== 206) {
|
|
96868
|
+
throw new RangeNotSupportedError(`Server did not honor range request (status ${response.status})`);
|
|
96869
|
+
}
|
|
96870
|
+
if (!response.body) {
|
|
96871
|
+
throw new Error(`Range request returned no body for ${filePath}`);
|
|
96872
|
+
}
|
|
96873
|
+
const input = Readable.fromWeb(response.body);
|
|
96874
|
+
const meter = watchStreamProgress(25 * 1024 * 1024); // 25 MiB intervals
|
|
96875
|
+
const output = createWriteStream(partialPath, { flags: "a" });
|
|
96876
|
+
let progressTimeout = null;
|
|
96877
|
+
let lastPercentage = ((startOffset / fileSize) * 100).toFixed(1);
|
|
96878
|
+
let lastProgressBytes = startOffset;
|
|
96879
|
+
const resetProgressTimeout = () => {
|
|
96880
|
+
clearTimeout(progressTimeout);
|
|
96881
|
+
progressTimeout = setTimeout(() => {
|
|
96882
|
+
input.destroy(new Error(`Timed out with no progress for ${DOWNLOAD_PROGRESS_TIMEOUT}ms while downloading ${filePath}. Last progress: ${lastPercentage}% (${lastProgressBytes} / ${fileSize})`));
|
|
96883
|
+
}, DOWNLOAD_PROGRESS_TIMEOUT);
|
|
96884
|
+
};
|
|
96885
|
+
input.on("data", resetProgressTimeout);
|
|
96886
|
+
resetProgressTimeout();
|
|
96887
|
+
meter.progress.on("progress", (totalBytes) => {
|
|
96888
|
+
lastProgressBytes = startOffset + totalBytes;
|
|
96889
|
+
const percentComplete = ((lastProgressBytes / fileSize) * 100).toFixed(1);
|
|
96890
|
+
if (lastPercentage !== percentComplete) {
|
|
96891
|
+
console.log(` => ${percentComplete}% (${lastProgressBytes} / ${fileSize})`);
|
|
96892
|
+
}
|
|
96893
|
+
lastPercentage = percentComplete;
|
|
96894
|
+
});
|
|
96895
|
+
try {
|
|
96896
|
+
await pipeline(input, meter, output);
|
|
96897
|
+
}
|
|
96898
|
+
finally {
|
|
96899
|
+
clearTimeout(progressTimeout);
|
|
96900
|
+
}
|
|
96901
|
+
const updatedStats = await stat(partialPath);
|
|
96902
|
+
if (updatedStats.size < fileSize) {
|
|
96903
|
+
throw new Error(`Download incomplete for ${filePath}. Received ${updatedStats.size} / ${fileSize} bytes`);
|
|
96904
|
+
}
|
|
96905
|
+
await rename(partialPath, finalPath);
|
|
96906
|
+
return;
|
|
96907
|
+
}
|
|
96908
|
+
catch (error) {
|
|
96909
|
+
lastError = asError(error);
|
|
96910
|
+
if (lastError instanceof RangeNotSupportedError) {
|
|
96911
|
+
throw lastError;
|
|
96912
|
+
}
|
|
96913
|
+
if (attempt < DOWNLOAD_RETRY_ATTEMPTS_RANGE) {
|
|
96914
|
+
console.warn(`Retrying range download (${attempt}/${DOWNLOAD_RETRY_ATTEMPTS_RANGE}) for ${filePath} due to error: ${lastError.message}`);
|
|
96915
|
+
continue;
|
|
96916
|
+
}
|
|
96917
|
+
}
|
|
96918
|
+
}
|
|
96919
|
+
const errorMessage = `Failed downloading ${filePath} (${fileSize} bytes) after ${DOWNLOAD_RETRY_ATTEMPTS_RANGE} attempts. Last error: ${lastError?.message ?? "Unknown error"}`;
|
|
96920
|
+
throw new Error(errorMessage);
|
|
96921
|
+
}
|
|
96574
96922
|
|
|
96575
96923
|
const balanced = (a, b, str) => {
|
|
96576
96924
|
const ma = a instanceof RegExp ? maybeMatch(a, str) : a;
|