@beam-network/sdk 0.5.15 → 0.5.17-dev.0
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/README.md +69 -2
- package/dist/client.d.ts +18 -0
- package/dist/client.js +248 -6
- package/dist/huggingface.d.ts +101 -0
- package/dist/huggingface.js +298 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/models.d.ts +53 -1
- package/dist/models.js +19 -0
- package/dist/nats-control.d.ts +2 -2
- package/dist/nats-control.js +2 -2
- package/dist/provider-signing.d.ts +39 -1
- package/dist/provider-signing.js +116 -6
- package/package.json +1 -1
package/dist/provider-signing.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { AbortMultipartUploadCommand, CompleteMultipartUploadCommand, CreateMultipartUploadCommand, GetObjectCommand, HeadObjectCommand, ListPartsCommand, PutObjectCommand, S3Client, UploadPartCommand } from "@aws-sdk/client-s3";
|
|
2
2
|
import { getSignedUrl } from "@aws-sdk/s3-request-presigner";
|
|
3
|
+
import { huggingFaceEndpoint, huggingFaceFileMetadata, huggingFaceRepoType, huggingFaceRevision } from "./huggingface.js";
|
|
3
4
|
export function nowIso() {
|
|
4
5
|
return new Date().toISOString();
|
|
5
6
|
}
|
|
@@ -47,6 +48,18 @@ export async function prepareProviderSource(source, options = {}) {
|
|
|
47
48
|
metadata: { bucket: source.bucket, key: source.key, base_url: baseUrl }
|
|
48
49
|
};
|
|
49
50
|
}
|
|
51
|
+
if (isHuggingFaceProvider(source)) {
|
|
52
|
+
const metadata = await huggingFaceFileMetadata(options.fetchImpl ?? globalThis.fetch, source);
|
|
53
|
+
return {
|
|
54
|
+
source_id: source.id ?? `src_${index}`,
|
|
55
|
+
type: "http",
|
|
56
|
+
provider: "huggingface",
|
|
57
|
+
url: metadata.url,
|
|
58
|
+
size: metadata.size,
|
|
59
|
+
filename: filename(source.path),
|
|
60
|
+
metadata: huggingFaceMetadata(source, metadata.etag, metadata.commitHash)
|
|
61
|
+
};
|
|
62
|
+
}
|
|
50
63
|
return unsupportedProviderConfig(source);
|
|
51
64
|
}
|
|
52
65
|
export async function prepareProviderSourceForPlan(source, options = {}) {
|
|
@@ -83,6 +96,17 @@ export async function prepareProviderSourceForPlan(source, options = {}) {
|
|
|
83
96
|
metadata: { bucket: source.bucket, key: source.key, base_url: baseUrl }
|
|
84
97
|
};
|
|
85
98
|
}
|
|
99
|
+
if (isHuggingFaceProvider(source)) {
|
|
100
|
+
const metadata = await huggingFaceFileMetadata(options.fetchImpl ?? globalThis.fetch, source);
|
|
101
|
+
return {
|
|
102
|
+
source_id: source.id ?? `src_${index}`,
|
|
103
|
+
type: "http",
|
|
104
|
+
provider: "huggingface",
|
|
105
|
+
size: metadata.size,
|
|
106
|
+
filename: filename(source.path),
|
|
107
|
+
metadata: huggingFaceMetadata(source, metadata.etag, metadata.commitHash)
|
|
108
|
+
};
|
|
109
|
+
}
|
|
86
110
|
return unsupportedProviderConfig(source);
|
|
87
111
|
}
|
|
88
112
|
export function prepareProviderDestination(destination, options = {}) {
|
|
@@ -111,6 +135,14 @@ export function prepareProviderDestination(destination, options = {}) {
|
|
|
111
135
|
}
|
|
112
136
|
};
|
|
113
137
|
}
|
|
138
|
+
if (isHuggingFaceProvider(destination)) {
|
|
139
|
+
return {
|
|
140
|
+
destination_id: destinationId,
|
|
141
|
+
provider: "huggingface",
|
|
142
|
+
logical_prefix: destination.path,
|
|
143
|
+
metadata: huggingFaceMetadata(destination)
|
|
144
|
+
};
|
|
145
|
+
}
|
|
114
146
|
return unsupportedProviderConfig(destination);
|
|
115
147
|
}
|
|
116
148
|
export async function createMultipartUpload(input) {
|
|
@@ -169,13 +201,51 @@ export async function abortMultipartUpload(destination, objectKey, uploadId) {
|
|
|
169
201
|
}
|
|
170
202
|
throw new Error(`multipart abort is not supported for ${destination.provider}`);
|
|
171
203
|
}
|
|
204
|
+
/** Provider metadata only. These operations never read or proxy object bytes. */
|
|
205
|
+
export async function listMultipartParts(destination, objectKey, uploadId) {
|
|
206
|
+
if (!isS3CompatibleProvider(destination))
|
|
207
|
+
return unsupportedProviderConfig(destination);
|
|
208
|
+
const parts = [];
|
|
209
|
+
let marker;
|
|
210
|
+
do {
|
|
211
|
+
const page = await createS3CompatibleClient(destination).send(new ListPartsCommand({
|
|
212
|
+
Bucket: destination.bucket, Key: objectKey, UploadId: uploadId, MaxParts: 1000, PartNumberMarker: marker
|
|
213
|
+
}));
|
|
214
|
+
for (const part of page.Parts ?? []) {
|
|
215
|
+
if (!part.PartNumber || !part.ETag || !Number.isSafeInteger(part.Size))
|
|
216
|
+
throw new Error("invalid multipart part metadata");
|
|
217
|
+
parts.push({ partNumber: part.PartNumber, etag: part.ETag, size: part.Size });
|
|
218
|
+
}
|
|
219
|
+
const next = page.IsTruncated ? page.NextPartNumberMarker : undefined;
|
|
220
|
+
if (page.IsTruncated && (!next || next === marker))
|
|
221
|
+
throw new Error("invalid multipart pagination");
|
|
222
|
+
marker = next;
|
|
223
|
+
} while (marker);
|
|
224
|
+
return parts;
|
|
225
|
+
}
|
|
226
|
+
export async function completeMultipartUpload(input) {
|
|
227
|
+
if (!isS3CompatibleProvider(input.destination))
|
|
228
|
+
return unsupportedProviderConfig(input.destination);
|
|
229
|
+
const result = await createS3CompatibleClient(input.destination).send(new CompleteMultipartUploadCommand({
|
|
230
|
+
Bucket: input.destination.bucket, Key: input.objectKey, UploadId: input.uploadId,
|
|
231
|
+
MultipartUpload: { Parts: [...input.parts].sort((a, b) => a.partNumber - b.partNumber)
|
|
232
|
+
.map((part) => ({ PartNumber: part.partNumber, ETag: part.etag })) }
|
|
233
|
+
}));
|
|
234
|
+
return { etag: result.ETag, versionId: result.VersionId };
|
|
235
|
+
}
|
|
236
|
+
export async function inspectDestinationObject(destination, objectKey) {
|
|
237
|
+
if (!isS3CompatibleProvider(destination))
|
|
238
|
+
return unsupportedProviderConfig(destination);
|
|
239
|
+
const result = await createS3CompatibleClient(destination).send(new HeadObjectCommand({ Bucket: destination.bucket, Key: objectKey }));
|
|
240
|
+
return { size: result.ContentLength, etag: result.ETag, versionId: result.VersionId, metadata: result.Metadata ?? {} };
|
|
241
|
+
}
|
|
172
242
|
export async function signDestinationRoute(input) {
|
|
173
243
|
const targetObjectKey = input.target.object_key;
|
|
174
244
|
if (!targetObjectKey) {
|
|
175
245
|
throw new Error("destination signing target is missing object_key");
|
|
176
246
|
}
|
|
177
247
|
const [destUrl, sourceRoute] = await Promise.all([
|
|
178
|
-
signDestinationUrl({
|
|
248
|
+
input.destUrl ?? signDestinationUrl({
|
|
179
249
|
destination: input.destination,
|
|
180
250
|
objectKey: targetObjectKey,
|
|
181
251
|
uploadId: input.uploadId,
|
|
@@ -234,17 +304,25 @@ export async function signSourceReadRange(input) {
|
|
|
234
304
|
url: await getSignedUrl(client, new GetObjectCommand({
|
|
235
305
|
Bucket: input.source.bucket,
|
|
236
306
|
Key: input.source.key,
|
|
237
|
-
Range: range
|
|
307
|
+
Range: range,
|
|
308
|
+
IfMatch: input.ifMatch,
|
|
309
|
+
VersionId: input.versionId
|
|
238
310
|
}), { expiresIn: input.expiresIn }),
|
|
239
|
-
headers: { Range: range }
|
|
311
|
+
headers: { Range: range, ...(input.ifMatch ? { "If-Match": input.ifMatch } : {}) }
|
|
240
312
|
};
|
|
241
313
|
}
|
|
314
|
+
if (input.ifMatch || input.versionId)
|
|
315
|
+
throw new Error("conditional source ranges require S3-compatible storage");
|
|
242
316
|
if (isHippiusProvider(input.source)) {
|
|
243
317
|
return {
|
|
244
318
|
url: await hippiusPresign(input.fetchImpl ?? globalThis.fetch, input.source.base_url ?? "https://api.hippius.com", input.source.api_token, input.source.bucket, input.source.key, "get", input.expiresIn),
|
|
245
319
|
headers: { Range: range }
|
|
246
320
|
};
|
|
247
321
|
}
|
|
322
|
+
if (isHuggingFaceProvider(input.source)) {
|
|
323
|
+
const metadata = await huggingFaceFileMetadata(input.fetchImpl ?? globalThis.fetch, input.source);
|
|
324
|
+
return { url: metadata.url, headers: { Range: range } };
|
|
325
|
+
}
|
|
248
326
|
return unsupportedProviderConfig(input.source);
|
|
249
327
|
}
|
|
250
328
|
export async function signDestinationReadRange(input) {
|
|
@@ -266,6 +344,15 @@ export async function signDestinationReadRange(input) {
|
|
|
266
344
|
headers: { Range: range }
|
|
267
345
|
};
|
|
268
346
|
}
|
|
347
|
+
if (isHuggingFaceProvider(input.destination)) {
|
|
348
|
+
// Read-back resolves the committed file. Before the commit lands the bytes exist only as
|
|
349
|
+
// uncommitted LFS parts, which the Hub does not expose.
|
|
350
|
+
const metadata = await huggingFaceFileMetadata(input.fetchImpl ?? globalThis.fetch, {
|
|
351
|
+
...input.destination,
|
|
352
|
+
path: input.objectKey
|
|
353
|
+
});
|
|
354
|
+
return { url: metadata.url, headers: { Range: range } };
|
|
355
|
+
}
|
|
269
356
|
return unsupportedProviderConfig(input.destination);
|
|
270
357
|
}
|
|
271
358
|
async function signSourceRoute(input) {
|
|
@@ -290,9 +377,13 @@ async function signSourceRoute(input) {
|
|
|
290
377
|
headers: { Range: range }
|
|
291
378
|
};
|
|
292
379
|
}
|
|
380
|
+
if (isHuggingFaceProvider(input.source)) {
|
|
381
|
+
const metadata = await huggingFaceFileMetadata(input.fetchImpl ?? globalThis.fetch, input.source);
|
|
382
|
+
return { url: metadata.url, headers: { Range: range } };
|
|
383
|
+
}
|
|
293
384
|
return unsupportedProviderConfig(input.source);
|
|
294
385
|
}
|
|
295
|
-
async function signDestinationUrl(input) {
|
|
386
|
+
export async function signDestinationUrl(input) {
|
|
296
387
|
if (isS3CompatibleProvider(input.destination)) {
|
|
297
388
|
const client = createS3CompatibleClient(input.destination);
|
|
298
389
|
if (input.uploadId && input.partNumber) {
|
|
@@ -300,11 +391,14 @@ async function signDestinationUrl(input) {
|
|
|
300
391
|
Bucket: input.destination.bucket,
|
|
301
392
|
Key: input.objectKey,
|
|
302
393
|
UploadId: input.uploadId,
|
|
303
|
-
PartNumber: input.partNumber
|
|
394
|
+
PartNumber: input.partNumber,
|
|
395
|
+
ContentMD5: input.contentMd5
|
|
304
396
|
}), { expiresIn: input.expiresIn });
|
|
305
397
|
}
|
|
306
|
-
return getSignedUrl(client, new PutObjectCommand({ Bucket: input.destination.bucket, Key: input.objectKey }), { expiresIn: input.expiresIn });
|
|
398
|
+
return getSignedUrl(client, new PutObjectCommand({ Bucket: input.destination.bucket, Key: input.objectKey, ContentMD5: input.contentMd5 }), { expiresIn: input.expiresIn });
|
|
307
399
|
}
|
|
400
|
+
if (input.contentMd5)
|
|
401
|
+
throw new Error("checksum-bound uploads require S3-compatible storage");
|
|
308
402
|
if (isHippiusProvider(input.destination)) {
|
|
309
403
|
return hippiusPresign(input.fetchImpl ?? globalThis.fetch, input.destination.base_url ?? "https://api.hippius.com", input.destination.api_token, input.destination.bucket, input.objectKey, "put", input.expiresIn);
|
|
310
404
|
}
|
|
@@ -396,6 +490,22 @@ function s3CompatibleMetadata(source, endpoint = s3CompatibleEndpoint(source)) {
|
|
|
396
490
|
function isHippiusProvider(config) {
|
|
397
491
|
return config.provider === "hippius" && "api_token" in config;
|
|
398
492
|
}
|
|
493
|
+
export function isHuggingFaceProvider(config) {
|
|
494
|
+
return config.provider === "huggingface" && "repo_id" in config;
|
|
495
|
+
}
|
|
496
|
+
function huggingFaceMetadata(config, etag, commitHash) {
|
|
497
|
+
return compactMetadata({
|
|
498
|
+
driver: "huggingface",
|
|
499
|
+
repo_id: config.repo_id,
|
|
500
|
+
repo_type: huggingFaceRepoType(config),
|
|
501
|
+
revision: huggingFaceRevision(config),
|
|
502
|
+
path: config.path,
|
|
503
|
+
endpoint: huggingFaceEndpoint(config),
|
|
504
|
+
// For an LFS blob the Hub's linked ETag is the object's sha256.
|
|
505
|
+
sha256: etag,
|
|
506
|
+
commit_hash: commitHash
|
|
507
|
+
});
|
|
508
|
+
}
|
|
399
509
|
async function hippiusPresign(fetchImpl, baseUrl, token, bucket, key, action, expiresIn) {
|
|
400
510
|
const url = new URL(`/api/objectstore/buckets/${bucket}/presigned-url/`, baseUrl);
|
|
401
511
|
url.searchParams.set("key", key);
|