-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathnative-downloader.ts
More file actions
119 lines (107 loc) · 4.06 KB
/
Copy pathnative-downloader.ts
File metadata and controls
119 lines (107 loc) · 4.06 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
import * as path from "node:path";
import { classifyLoadError, type NativeCapabilityReport } from "./native-capability";
export interface NativeDownloadProgress {
receivedBytes: number;
totalBytes?: number;
}
export interface JobEvent {
jobId: string;
kind: "shard_progress" | "shard_state" | "job_state" | "job_error";
shardFilename?: string;
shardState?: string;
jobState?: string;
receivedBytes?: number;
jobReceivedBytes?: number;
totalBytes?: number;
bytesPerSec?: number;
etaSeconds?: number;
errorMessage?: string;
errorKind?: string;
retryable?: boolean;
}
export interface JsShard {
filename: string;
path: string;
expectedBytes: number;
receivedBytes: number;
sha256?: string;
}
export interface JsDownloadJob {
id: string;
modelId: string;
token?: string;
destinationDir: string;
shards: JsShard[];
}
export interface NativeDownloadManager {
setGlobalConcurrency(limit: number): void;
setBandwidthLimit(bytesPerSec: number | undefined | null): void;
pauseJob(jobId: string): void;
cancelJob(jobId: string): void;
startJob(job: JsDownloadJob, onEvent: (err: Error | null, event: JobEvent) => void): Promise<void>;
}
interface NativeAddon {
downloadGgufFile(
modelId: string,
filename: string,
destPath: string,
token: string | undefined | null,
expectedSha256: string | undefined | null,
onProgress: (err: Error | null, progress: NativeDownloadProgress) => void
): Promise<void>;
DownloadManager: new () => NativeDownloadManager;
}
// The Rust addon is built by `lib`'s `napi build` step into app/native/ as a
// plain filesystem artifact (not an npm dependency under node_modules —
// see the plan for why: a `file:` dependency would need `npm ci` to
// reconcile a different lockfile integrity hash per platform's .node
// binary). `dist/native-downloader.js` sits one level under app/, so
// app/native is a sibling reached by going up just one directory. Electron
// transparently redirects native-module requires from inside app.asar to
// the asarUnpack'd app.asar.unpacked counterpart at packaging time.
let nativeAddon: NativeAddon | undefined;
function getNativeAddon(): NativeAddon {
if (!nativeAddon) {
nativeAddon = require(path.join(__dirname, "..", "native")) as NativeAddon;
}
return nativeAddon;
}
export function downloadGgufFileNative(
modelId: string,
filename: string,
destPath: string,
token: string | undefined | null,
expectedSha256: string | undefined | null,
onProgress: (err: Error | null, progress: NativeDownloadProgress) => void
): Promise<void> {
return getNativeAddon().downloadGgufFile(modelId, filename, destPath, token, expectedSha256, onProgress);
}
// One instance for the app's lifetime — its global concurrency semaphore
// and bandwidth limiter are only meaningfully "global" if nothing else
// constructs a second one. Purely additive to `downloadGgufFileNative`
// above, which stays exactly as-is for the existing single-file `hf:
// downloadFile` flow.
let downloadManager: NativeDownloadManager | undefined;
export function getDownloadManager(): NativeDownloadManager {
if (!downloadManager) {
const { DownloadManager } = getNativeAddon();
downloadManager = new DownloadManager();
}
return downloadManager;
}
/**
* Diagnostic-only, non-throwing check of whether the native download engine
* is available and, if not, why — see native-capability.ts. Every real
* caller above (`downloadGgufFileNative`, `getDownloadManager`) still
* throws on failure as before; this exists so callers that want to log or
* report the reason (rather than just catch-and-fall-back) can, without
* duplicating the classification logic.
*/
export function getNativeDownloaderCapabilityReport(): NativeCapabilityReport {
try {
getNativeAddon();
return { available: true };
} catch (err) {
return { available: false, reason: classifyLoadError(err), detail: err instanceof Error ? err.message : String(err) };
}
}