@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
|
@@ -0,0 +1,298 @@
|
|
|
1
|
+
export const HUGGINGFACE_DEFAULT_ENDPOINT = "https://huggingface.co";
|
|
2
|
+
export const HUGGINGFACE_DEFAULT_REVISION = "main";
|
|
3
|
+
export const HUGGINGFACE_DEFAULT_REPO_TYPE = "model";
|
|
4
|
+
const LFS_CONTENT_TYPE = "application/vnd.git-lfs+json";
|
|
5
|
+
/** Mirrors `constants.REPO_TYPES_URL_PREFIXES`; models carry no prefix. */
|
|
6
|
+
const REPO_TYPE_URL_PREFIXES = {
|
|
7
|
+
model: "",
|
|
8
|
+
dataset: "datasets/",
|
|
9
|
+
space: "spaces/",
|
|
10
|
+
kernel: "kernels/",
|
|
11
|
+
bucket: "buckets/"
|
|
12
|
+
};
|
|
13
|
+
export function huggingFaceEndpoint(config) {
|
|
14
|
+
return (config.endpoint ?? HUGGINGFACE_DEFAULT_ENDPOINT).replace(/\/+$/, "");
|
|
15
|
+
}
|
|
16
|
+
export function huggingFaceRepoType(config) {
|
|
17
|
+
return config.repo_type ?? HUGGINGFACE_DEFAULT_REPO_TYPE;
|
|
18
|
+
}
|
|
19
|
+
export function huggingFaceRevision(config) {
|
|
20
|
+
return config.revision ?? HUGGINGFACE_DEFAULT_REVISION;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* `{endpoint}/{prefix}{repo_id}/resolve/{revision}/{path}`, as built by `hf_hub_url`.
|
|
24
|
+
*
|
|
25
|
+
* Buckets are unversioned and take no revision segment, and the Hub escapes their whole key
|
|
26
|
+
* as one component — see `HfApi.get_bucket_file_metadata`.
|
|
27
|
+
*/
|
|
28
|
+
export function huggingFaceResolveUrl(config) {
|
|
29
|
+
const repoType = huggingFaceRepoType(config);
|
|
30
|
+
const prefix = REPO_TYPE_URL_PREFIXES[repoType];
|
|
31
|
+
if (repoType === "bucket") {
|
|
32
|
+
return `${huggingFaceEndpoint(config)}/${prefix}${config.repo_id}/resolve/${encodeURIComponent(config.path)}`;
|
|
33
|
+
}
|
|
34
|
+
const revision = encodeURIComponent(huggingFaceRevision(config));
|
|
35
|
+
return `${huggingFaceEndpoint(config)}/${prefix}${config.repo_id}/resolve/${revision}/${encodePath(config.path)}`;
|
|
36
|
+
}
|
|
37
|
+
/** `{endpoint}/api/{repo_type}s/{repo_id}`. */
|
|
38
|
+
export function huggingFaceApiBase(config) {
|
|
39
|
+
return `${huggingFaceEndpoint(config)}/api/${huggingFaceRepoType(config)}s/${config.repo_id}`;
|
|
40
|
+
}
|
|
41
|
+
/** `{endpoint}/{prefix}{repo_id}.git/info/lfs/objects/batch`. */
|
|
42
|
+
export function huggingFaceLfsBatchUrl(config) {
|
|
43
|
+
const prefix = REPO_TYPE_URL_PREFIXES[huggingFaceRepoType(config)];
|
|
44
|
+
return `${huggingFaceEndpoint(config)}/${prefix}${config.repo_id}.git/info/lfs/objects/batch`;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* HEAD the resolve URL and require the Hub to redirect to its CDN. The redirect target is
|
|
48
|
+
* presigned and carries no credential, so it is the only form of this URL that may be handed
|
|
49
|
+
* to BeamCore and the workers.
|
|
50
|
+
*/
|
|
51
|
+
export async function huggingFaceFileMetadata(fetchImpl, config) {
|
|
52
|
+
const resolveUrl = huggingFaceResolveUrl(config);
|
|
53
|
+
const response = await fetchImpl(resolveUrl, {
|
|
54
|
+
method: "HEAD",
|
|
55
|
+
redirect: "manual",
|
|
56
|
+
headers: {
|
|
57
|
+
Authorization: `Bearer ${config.token}`,
|
|
58
|
+
// Compression would report a transformed length instead of the real object size.
|
|
59
|
+
"Accept-Encoding": "identity"
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
if (response.status >= 400) {
|
|
63
|
+
throw new Error(`Hugging Face file lookup failed status=${response.status} for ${describe(config)}`);
|
|
64
|
+
}
|
|
65
|
+
const location = response.headers.get("location");
|
|
66
|
+
if (!location || sameHost(resolveUrl, location)) {
|
|
67
|
+
throw new Error(`Hugging Face did not redirect ${describe(config)} to a presigned CDN URL. `
|
|
68
|
+
+ "Beam reads this file over plain HTTP without forwarding your token, and the Hub only "
|
|
69
|
+
+ "redirects for large-file (LFS or Xet) content. A small regular file is served inline "
|
|
70
|
+
+ "from the Hub instead and cannot be transferred.");
|
|
71
|
+
}
|
|
72
|
+
const linkedSize = response.headers.get("x-linked-size");
|
|
73
|
+
const size = Number(linkedSize ?? response.headers.get("content-length") ?? Number.NaN);
|
|
74
|
+
if (!Number.isFinite(size) || size <= 0) {
|
|
75
|
+
throw new Error(`Hugging Face did not report a size for ${describe(config)}`);
|
|
76
|
+
}
|
|
77
|
+
return {
|
|
78
|
+
url: location,
|
|
79
|
+
size,
|
|
80
|
+
etag: normalizeEtag(response.headers.get("x-linked-etag") ?? response.headers.get("etag")),
|
|
81
|
+
commitHash: response.headers.get("x-repo-commit") ?? undefined
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Ask the Hub whether a path is stored as an LFS blob or as a regular git blob.
|
|
86
|
+
* `sample` is the base64 of the first 512 bytes, exactly as `_fetch_upload_modes` sends it.
|
|
87
|
+
*/
|
|
88
|
+
export async function huggingFacePreupload(fetchImpl, config, input) {
|
|
89
|
+
const url = new URL(`${huggingFaceApiBase(config)}/preupload/${encodeURIComponent(huggingFaceRevision(config))}`);
|
|
90
|
+
if (config.create_pr)
|
|
91
|
+
url.searchParams.set("create_pr", "1");
|
|
92
|
+
const response = await fetchImpl(url, {
|
|
93
|
+
method: "POST",
|
|
94
|
+
headers: jsonHeaders(config),
|
|
95
|
+
body: JSON.stringify({ files: [{ path: config.path, sample: input.sample, size: input.size }] })
|
|
96
|
+
});
|
|
97
|
+
const payload = await readJson(response, "preupload", config);
|
|
98
|
+
const file = Array.isArray(payload.files) ? payload.files[0] : undefined;
|
|
99
|
+
if (!file || (file.uploadMode !== "lfs" && file.uploadMode !== "regular")) {
|
|
100
|
+
throw new Error(`Hugging Face preupload returned a malformed response for ${describe(config)}`);
|
|
101
|
+
}
|
|
102
|
+
return {
|
|
103
|
+
uploadMode: file.uploadMode,
|
|
104
|
+
shouldIgnore: Boolean(file.shouldIgnore),
|
|
105
|
+
oid: typeof file.oid === "string" ? file.oid : undefined
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Request upload instructions for one object. The Hub answers with either a single-part PUT
|
|
110
|
+
* target or a completion endpoint plus one presigned PUT URL per part.
|
|
111
|
+
*/
|
|
112
|
+
export async function huggingFaceLfsBatch(fetchImpl, config, input) {
|
|
113
|
+
const response = await fetchImpl(huggingFaceLfsBatchUrl(config), {
|
|
114
|
+
method: "POST",
|
|
115
|
+
headers: {
|
|
116
|
+
Authorization: `Bearer ${config.token}`,
|
|
117
|
+
Accept: LFS_CONTENT_TYPE,
|
|
118
|
+
"Content-Type": LFS_CONTENT_TYPE
|
|
119
|
+
},
|
|
120
|
+
body: JSON.stringify({
|
|
121
|
+
operation: "upload",
|
|
122
|
+
transfers: ["basic", "multipart"],
|
|
123
|
+
hash_algo: "sha256",
|
|
124
|
+
ref: { name: huggingFaceRevision(config) },
|
|
125
|
+
objects: [{ oid: input.oid, size: input.size }]
|
|
126
|
+
})
|
|
127
|
+
});
|
|
128
|
+
const payload = await readJson(response, "LFS batch", config);
|
|
129
|
+
const object = Array.isArray(payload.objects) ? payload.objects[0] : undefined;
|
|
130
|
+
if (!object || typeof object.oid !== "string") {
|
|
131
|
+
throw new Error(`Hugging Face LFS batch returned a malformed response for ${describe(config)}`);
|
|
132
|
+
}
|
|
133
|
+
if (object.error) {
|
|
134
|
+
throw new Error(`Hugging Face LFS batch rejected ${describe(config)}: ${object.error.message ?? "unknown error"}`);
|
|
135
|
+
}
|
|
136
|
+
const uploadAction = object.actions?.upload;
|
|
137
|
+
if (!uploadAction) {
|
|
138
|
+
// No actions means the Hub already stores this content; only the commit is left to do.
|
|
139
|
+
return { oid: object.oid, size: input.size };
|
|
140
|
+
}
|
|
141
|
+
if (typeof uploadAction.href !== "string") {
|
|
142
|
+
throw new Error(`Hugging Face LFS batch returned no upload href for ${describe(config)}`);
|
|
143
|
+
}
|
|
144
|
+
const header = uploadAction.header ?? {};
|
|
145
|
+
const rawChunkSize = header.chunk_size;
|
|
146
|
+
const chunkSize = rawChunkSize === undefined ? undefined : Number(rawChunkSize);
|
|
147
|
+
if (chunkSize !== undefined && (!Number.isInteger(chunkSize) || chunkSize <= 0)) {
|
|
148
|
+
throw new Error(`Hugging Face LFS batch returned a malformed chunk_size '${rawChunkSize}' for ${describe(config)}`);
|
|
149
|
+
}
|
|
150
|
+
const partUrls = Object.entries(header)
|
|
151
|
+
.filter(([key]) => /^\d+$/.test(key))
|
|
152
|
+
.sort(([left], [right]) => Number(left) - Number(right))
|
|
153
|
+
.map(([, value]) => value);
|
|
154
|
+
if (chunkSize !== undefined && partUrls.length !== Math.ceil(input.size / chunkSize)) {
|
|
155
|
+
throw new Error(`Hugging Face returned ${partUrls.length} part URLs for ${describe(config)}, `
|
|
156
|
+
+ `expected ${Math.ceil(input.size / chunkSize)} at chunk_size ${chunkSize}`);
|
|
157
|
+
}
|
|
158
|
+
return {
|
|
159
|
+
oid: object.oid,
|
|
160
|
+
size: input.size,
|
|
161
|
+
upload: { href: uploadAction.href, chunkSize, partUrls },
|
|
162
|
+
verifyHref: typeof object.actions?.verify?.href === "string" ? object.actions.verify.href : undefined
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
/** Close a multipart LFS upload: `{oid, parts:[{partNumber, etag}]}` to the completion href. */
|
|
166
|
+
export async function huggingFaceCompleteLfsUpload(fetchImpl, config, input) {
|
|
167
|
+
const response = await fetchImpl(input.href, {
|
|
168
|
+
method: "POST",
|
|
169
|
+
headers: { Accept: LFS_CONTENT_TYPE, "Content-Type": LFS_CONTENT_TYPE },
|
|
170
|
+
body: JSON.stringify({
|
|
171
|
+
oid: input.oid,
|
|
172
|
+
parts: input.etags.map((etag, index) => ({ partNumber: index + 1, etag }))
|
|
173
|
+
})
|
|
174
|
+
});
|
|
175
|
+
await assertOk(response, "LFS completion", config);
|
|
176
|
+
}
|
|
177
|
+
/** Optional server-side check that the object landed intact. */
|
|
178
|
+
export async function huggingFaceVerifyLfsUpload(fetchImpl, config, input) {
|
|
179
|
+
const response = await fetchImpl(input.href, {
|
|
180
|
+
method: "POST",
|
|
181
|
+
headers: jsonHeaders(config),
|
|
182
|
+
body: JSON.stringify({ oid: input.oid, size: input.size })
|
|
183
|
+
});
|
|
184
|
+
await assertOk(response, "LFS verify", config);
|
|
185
|
+
}
|
|
186
|
+
/** Publish the uploaded blob as a commit. The body is NDJSON: a header line then one file line. */
|
|
187
|
+
export async function huggingFaceCommit(fetchImpl, config, input) {
|
|
188
|
+
const url = new URL(`${huggingFaceApiBase(config)}/commit/${encodeURIComponent(huggingFaceRevision(config))}`);
|
|
189
|
+
if (config.create_pr)
|
|
190
|
+
url.searchParams.set("create_pr", "1");
|
|
191
|
+
const lines = [
|
|
192
|
+
{
|
|
193
|
+
key: "header",
|
|
194
|
+
value: {
|
|
195
|
+
summary: config.commit_message ?? `Upload ${config.path} with Beam`,
|
|
196
|
+
description: config.commit_description ?? ""
|
|
197
|
+
}
|
|
198
|
+
},
|
|
199
|
+
{
|
|
200
|
+
key: "lfsFile",
|
|
201
|
+
value: { path: config.path, algo: "sha256", oid: input.oid, size: input.size }
|
|
202
|
+
}
|
|
203
|
+
];
|
|
204
|
+
const response = await fetchImpl(url, {
|
|
205
|
+
method: "POST",
|
|
206
|
+
headers: { Authorization: `Bearer ${config.token}`, "Content-Type": "application/x-ndjson" },
|
|
207
|
+
body: lines.map((line) => JSON.stringify(line)).join("\n")
|
|
208
|
+
});
|
|
209
|
+
const payload = await readJson(response, "commit", config);
|
|
210
|
+
return {
|
|
211
|
+
commitOid: typeof payload.commitOid === "string" ? payload.commitOid : undefined,
|
|
212
|
+
pullRequestUrl: typeof payload.pullRequestUrl === "string" ? payload.pullRequestUrl : undefined
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
/** Read the first `length` bytes of a URL and return them base64-encoded, for `preupload`. */
|
|
216
|
+
export async function readSourceSample(fetchImpl, url, length = 512) {
|
|
217
|
+
const response = await fetchImpl(url, { headers: { Range: `bytes=0-${length - 1}` } });
|
|
218
|
+
if (!response.ok) {
|
|
219
|
+
throw new Error(`source sample read failed status=${response.status}`);
|
|
220
|
+
}
|
|
221
|
+
return base64(new Uint8Array(await response.arrayBuffer()));
|
|
222
|
+
}
|
|
223
|
+
/**
|
|
224
|
+
* Stream a URL once and return the sha256 of the whole body plus, when `partSize` is given,
|
|
225
|
+
* the hex MD5 of every part. The Hub will not issue upload URLs without the sha256, and the
|
|
226
|
+
* per-part MD5 is the ETag the completion payload has to quote.
|
|
227
|
+
*/
|
|
228
|
+
export async function hashSourceStream(fetchImpl, url, options = {}) {
|
|
229
|
+
const { createHash } = await import("node:crypto");
|
|
230
|
+
const response = await fetchImpl(url);
|
|
231
|
+
if (!response.ok || !response.body) {
|
|
232
|
+
throw new Error(`source hash read failed status=${response.status}`);
|
|
233
|
+
}
|
|
234
|
+
const whole = options.sha256 === false ? null : createHash("sha256");
|
|
235
|
+
const partSize = options.partSize;
|
|
236
|
+
const partEtags = [];
|
|
237
|
+
let part = partSize ? createHash("md5") : null;
|
|
238
|
+
let partBytes = 0;
|
|
239
|
+
for await (const rawChunk of response.body) {
|
|
240
|
+
let chunk = rawChunk;
|
|
241
|
+
whole?.update(chunk);
|
|
242
|
+
while (partSize && part && chunk.length > 0) {
|
|
243
|
+
const room = partSize - partBytes;
|
|
244
|
+
if (chunk.length < room) {
|
|
245
|
+
part.update(chunk);
|
|
246
|
+
partBytes += chunk.length;
|
|
247
|
+
break;
|
|
248
|
+
}
|
|
249
|
+
part.update(chunk.subarray(0, room));
|
|
250
|
+
partEtags.push(part.digest("hex"));
|
|
251
|
+
part = createHash("md5");
|
|
252
|
+
partBytes = 0;
|
|
253
|
+
chunk = chunk.subarray(room);
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
if (partSize && part && partBytes > 0) {
|
|
257
|
+
partEtags.push(part.digest("hex"));
|
|
258
|
+
}
|
|
259
|
+
return { sha256: whole?.digest("hex"), partEtags };
|
|
260
|
+
}
|
|
261
|
+
function encodePath(path) {
|
|
262
|
+
return path.split("/").map(encodeURIComponent).join("/");
|
|
263
|
+
}
|
|
264
|
+
function sameHost(left, right) {
|
|
265
|
+
try {
|
|
266
|
+
return new URL(left).host === new URL(right, left).host;
|
|
267
|
+
}
|
|
268
|
+
catch {
|
|
269
|
+
return true;
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
function normalizeEtag(value) {
|
|
273
|
+
if (!value)
|
|
274
|
+
return undefined;
|
|
275
|
+
return value.replace(/^W\//, "").replace(/^"|"$/g, "");
|
|
276
|
+
}
|
|
277
|
+
function jsonHeaders(config) {
|
|
278
|
+
return { Authorization: `Bearer ${config.token}`, "Content-Type": "application/json" };
|
|
279
|
+
}
|
|
280
|
+
async function readJson(response, step, config) {
|
|
281
|
+
await assertOk(response, step, config);
|
|
282
|
+
return (await response.json());
|
|
283
|
+
}
|
|
284
|
+
async function assertOk(response, step, config) {
|
|
285
|
+
if (response.ok)
|
|
286
|
+
return;
|
|
287
|
+
const detail = (await response.text().catch(() => "")).slice(0, 512);
|
|
288
|
+
throw new Error(`Hugging Face ${step} failed status=${response.status} for ${describe(config)}${detail ? `: ${detail}` : ""}`);
|
|
289
|
+
}
|
|
290
|
+
function base64(bytes) {
|
|
291
|
+
let binary = "";
|
|
292
|
+
for (const byte of bytes)
|
|
293
|
+
binary += String.fromCharCode(byte);
|
|
294
|
+
return typeof btoa === "function" ? btoa(binary) : Buffer.from(bytes).toString("base64");
|
|
295
|
+
}
|
|
296
|
+
export function describe(config) {
|
|
297
|
+
return `${huggingFaceRepoType(config)} ${config.repo_id}@${huggingFaceRevision(config)}/${config.path}`;
|
|
298
|
+
}
|
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
package/dist/models.d.ts
CHANGED
|
@@ -182,6 +182,8 @@ export interface ProviderTransferResumeInput {
|
|
|
182
182
|
testMode?: boolean;
|
|
183
183
|
expiresIn?: number;
|
|
184
184
|
signedUrlFlow?: SignedUrlFlow;
|
|
185
|
+
/** Requested plan chunk size. BeamCore may raise it; the response carries the effective value. */
|
|
186
|
+
chunkSize?: number;
|
|
185
187
|
}
|
|
186
188
|
export interface TransferTerminalEvent {
|
|
187
189
|
schema_version: "transfer-client-control/v6";
|
|
@@ -237,13 +239,41 @@ export interface HippiusProviderConfig {
|
|
|
237
239
|
api_token: string;
|
|
238
240
|
base_url?: string;
|
|
239
241
|
}
|
|
240
|
-
export type
|
|
242
|
+
export type HuggingFaceRepoType = "model" | "dataset" | "space" | "kernel" | "bucket";
|
|
243
|
+
export interface HuggingFaceProviderConfig {
|
|
244
|
+
provider: "huggingface";
|
|
245
|
+
id?: string;
|
|
246
|
+
/** Namespace and repo or bucket name separated by a slash, for example `org/dataset`. */
|
|
247
|
+
repo_id: string;
|
|
248
|
+
/** Path of the file inside the repo. */
|
|
249
|
+
path: string;
|
|
250
|
+
/** Defaults to `model`. */
|
|
251
|
+
repo_type?: HuggingFaceRepoType;
|
|
252
|
+
/** Branch, tag or commit. Defaults to `main`. Buckets are unversioned and ignore it. */
|
|
253
|
+
revision?: string;
|
|
254
|
+
token: string;
|
|
255
|
+
/** Hub endpoint. Defaults to `https://huggingface.co`. */
|
|
256
|
+
endpoint?: string;
|
|
257
|
+
/** Destination only. Commit summary for the upload. */
|
|
258
|
+
commit_message?: string;
|
|
259
|
+
/** Destination only. Commit description for the upload. */
|
|
260
|
+
commit_description?: string;
|
|
261
|
+
/** Destination only. Open the upload as a pull request instead of committing to `revision`. */
|
|
262
|
+
create_pr?: boolean;
|
|
263
|
+
/**
|
|
264
|
+
* Destination only. The Hub issues LFS upload URLs only for a known sha256, so the SDK
|
|
265
|
+
* must read every source byte once to hash it. Opt in explicitly.
|
|
266
|
+
*/
|
|
267
|
+
allow_source_rehash?: boolean;
|
|
268
|
+
}
|
|
269
|
+
export type ProviderConfig = S3ProviderConfig | R2ProviderConfig | S3CompatibleProviderConfig | HippiusProviderConfig | HuggingFaceProviderConfig;
|
|
241
270
|
export type ProviderSourceConfig = ProviderConfig;
|
|
242
271
|
export type ProviderDestinationConfig = ProviderConfig;
|
|
243
272
|
export type S3ProviderConfigInput = Omit<S3ProviderConfig, "provider">;
|
|
244
273
|
export type R2ProviderConfigInput = Omit<R2ProviderConfig, "provider">;
|
|
245
274
|
export type S3CompatibleProviderConfigInput = Omit<S3CompatibleProviderConfig, "driver">;
|
|
246
275
|
export type HippiusProviderConfigInput = Omit<HippiusProviderConfig, "provider">;
|
|
276
|
+
export type HuggingFaceProviderConfigInput = Omit<HuggingFaceProviderConfig, "provider">;
|
|
247
277
|
export declare const S3ProviderConfig: Readonly<{
|
|
248
278
|
create(input: S3ProviderConfigInput): S3ProviderConfig;
|
|
249
279
|
}>;
|
|
@@ -256,6 +286,9 @@ export declare const S3CompatibleProviderConfig: Readonly<{
|
|
|
256
286
|
export declare const HippiusProviderConfig: Readonly<{
|
|
257
287
|
create(input: HippiusProviderConfigInput): HippiusProviderConfig;
|
|
258
288
|
}>;
|
|
289
|
+
export declare const HuggingFaceProviderConfig: Readonly<{
|
|
290
|
+
create(input: HuggingFaceProviderConfigInput): HuggingFaceProviderConfig;
|
|
291
|
+
}>;
|
|
259
292
|
export interface ProviderTransferCreateInput {
|
|
260
293
|
sources: ProviderSourceConfig[];
|
|
261
294
|
destinations: ProviderDestinationConfig[];
|
|
@@ -271,12 +304,31 @@ export interface ProviderTransferCreateInput {
|
|
|
271
304
|
distribute?: boolean;
|
|
272
305
|
onBeforeTransferPrepare?: () => void | Promise<void>;
|
|
273
306
|
onPrepared?: (prepared: TransferPrepareResponse) => void | Promise<void>;
|
|
307
|
+
/**
|
|
308
|
+
* Called after the SDK creates a multipart upload and before its signed
|
|
309
|
+
* routes are streamed. The payload contains durable identity only;
|
|
310
|
+
* credentials, headers, and signed URLs are never exposed.
|
|
311
|
+
*/
|
|
312
|
+
onMultipartGroupReady?: (group: ProviderMultipartGroupIdentity) => void | Promise<void>;
|
|
274
313
|
throwIfCancelled?: (transferId?: string) => void | Promise<void>;
|
|
275
314
|
signedUrlFlow?: SignedUrlFlow;
|
|
276
315
|
/** Stable caller identity used to derive the transfer and lifecycle request ids. */
|
|
277
316
|
idempotencyKey?: string;
|
|
278
317
|
/** Internal recovery generation; callers normally omit this. */
|
|
279
318
|
routeGenerationId?: string;
|
|
319
|
+
/** Requested plan chunk size. BeamCore may raise it; the response carries the effective value. */
|
|
320
|
+
chunkSize?: number;
|
|
321
|
+
}
|
|
322
|
+
export interface ProviderMultipartGroupIdentity {
|
|
323
|
+
transferId: string;
|
|
324
|
+
multipartGroupId: string;
|
|
325
|
+
sourceId: string;
|
|
326
|
+
destinationId: string;
|
|
327
|
+
objectKey: string;
|
|
328
|
+
uploadId: string;
|
|
329
|
+
expectedObjectSize: number;
|
|
330
|
+
expectedPartCount: number;
|
|
331
|
+
expiresAt: string;
|
|
280
332
|
}
|
|
281
333
|
export interface PlanningHttpSource {
|
|
282
334
|
source_id: string;
|
package/dist/models.js
CHANGED
|
@@ -38,6 +38,25 @@ export const HippiusProviderConfig = Object.freeze({
|
|
|
38
38
|
return { provider: "hippius", ...input };
|
|
39
39
|
}
|
|
40
40
|
});
|
|
41
|
+
const HUGGINGFACE_REPO_TYPES = [
|
|
42
|
+
"model",
|
|
43
|
+
"dataset",
|
|
44
|
+
"space",
|
|
45
|
+
"kernel",
|
|
46
|
+
"bucket"
|
|
47
|
+
];
|
|
48
|
+
export const HuggingFaceProviderConfig = Object.freeze({
|
|
49
|
+
create(input) {
|
|
50
|
+
requireFields("huggingface", input, ["repo_id", "path", "token"]);
|
|
51
|
+
if (input.repo_type !== undefined && !HUGGINGFACE_REPO_TYPES.includes(input.repo_type)) {
|
|
52
|
+
throw new Error(`huggingface config repo_type must be one of ${HUGGINGFACE_REPO_TYPES.join(", ")}.`);
|
|
53
|
+
}
|
|
54
|
+
if (!input.repo_id.includes("/")) {
|
|
55
|
+
throw new Error("huggingface config repo_id must be `namespace/name`.");
|
|
56
|
+
}
|
|
57
|
+
return { provider: "huggingface", ...input };
|
|
58
|
+
}
|
|
59
|
+
});
|
|
41
60
|
function requireFields(provider, input, fields) {
|
|
42
61
|
for (const field of fields) {
|
|
43
62
|
if (!hasText(input[field])) {
|
package/dist/nats-control.d.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { type ConnectionOptions } from "nats";
|
|
2
2
|
import type { MultipartGroupManifest, SignedChunkRoute, TransferTerminalSignalWaiter } from "./models.js";
|
|
3
3
|
export declare const TRANSFER_CLIENT_CONTROL_SCHEMA_VERSION = "transfer-client-control/v6";
|
|
4
|
-
export declare const BEAM_DEFAULT_NATS_URL = "tls://
|
|
5
|
-
export declare const BEAM_DEFAULT_NATS_WS_URL = "wss://
|
|
4
|
+
export declare const BEAM_DEFAULT_NATS_URL = "tls://orch-gateway.b1m.ai:4222";
|
|
5
|
+
export declare const BEAM_DEFAULT_NATS_WS_URL = "wss://orch-gateway.b1m.ai:443";
|
|
6
6
|
export declare const BEAM_DEFAULT_MAX_PAYLOAD_BYTES: number;
|
|
7
7
|
export declare const BEAM_ROUTE_TARGET_PAYLOAD_BYTES: number;
|
|
8
8
|
export declare const ROUTE_RECOVERY_SIGN_MESSAGE_TYPE: "transfer.route_recovery.sign";
|
package/dist/nats-control.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { encode, decode } from "@msgpack/msgpack";
|
|
2
2
|
import { connect } from "nats";
|
|
3
3
|
export const TRANSFER_CLIENT_CONTROL_SCHEMA_VERSION = "transfer-client-control/v6";
|
|
4
|
-
export const BEAM_DEFAULT_NATS_URL = "tls://
|
|
5
|
-
export const BEAM_DEFAULT_NATS_WS_URL = "wss://
|
|
4
|
+
export const BEAM_DEFAULT_NATS_URL = "tls://orch-gateway.b1m.ai:4222";
|
|
5
|
+
export const BEAM_DEFAULT_NATS_WS_URL = "wss://orch-gateway.b1m.ai:443";
|
|
6
6
|
// NATS enforces max_payload per message. This guard splits signed-route control
|
|
7
7
|
// messages before the broker rejects them; transfer bytes never flow through NATS.
|
|
8
8
|
export const BEAM_DEFAULT_MAX_PAYLOAD_BYTES = 8 * 1024 * 1024;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { ChunkDestinationSigningTarget, ChunkSigningPlanItem, PlanningHttpSource, PreparedDestination, PreparedHttpSource, ProviderDestinationConfig, ProviderSourceConfig, R2ProviderConfig, S3CompatibleProviderConfig, S3ProviderConfig, SignedChunkRoute } from "./models.js";
|
|
1
|
+
import type { ChunkDestinationSigningTarget, ChunkSigningPlanItem, HuggingFaceProviderConfig, PlanningHttpSource, PreparedDestination, PreparedHttpSource, ProviderDestinationConfig, ProviderSourceConfig, R2ProviderConfig, S3CompatibleProviderConfig, S3ProviderConfig, SignedChunkRoute } from "./models.js";
|
|
2
2
|
export type AnyS3CompatibleProviderConfig = S3ProviderConfig | R2ProviderConfig | S3CompatibleProviderConfig;
|
|
3
3
|
export declare function nowIso(): string;
|
|
4
4
|
export declare function expiresAtIso(expiresIn: number): string;
|
|
@@ -27,6 +27,30 @@ export declare function signListMultipartUpload(destination: ProviderDestination
|
|
|
27
27
|
partNumberMarker?: number;
|
|
28
28
|
}): Promise<string>;
|
|
29
29
|
export declare function abortMultipartUpload(destination: ProviderDestinationConfig, objectKey: string, uploadId: string): Promise<void>;
|
|
30
|
+
/** Provider metadata only. These operations never read or proxy object bytes. */
|
|
31
|
+
export declare function listMultipartParts(destination: ProviderDestinationConfig, objectKey: string, uploadId: string): Promise<{
|
|
32
|
+
partNumber: number;
|
|
33
|
+
etag: string;
|
|
34
|
+
size: number;
|
|
35
|
+
}[]>;
|
|
36
|
+
export declare function completeMultipartUpload(input: {
|
|
37
|
+
destination: ProviderDestinationConfig;
|
|
38
|
+
objectKey: string;
|
|
39
|
+
uploadId: string;
|
|
40
|
+
parts: {
|
|
41
|
+
partNumber: number;
|
|
42
|
+
etag: string;
|
|
43
|
+
}[];
|
|
44
|
+
}): Promise<{
|
|
45
|
+
etag: string | undefined;
|
|
46
|
+
versionId: string | undefined;
|
|
47
|
+
}>;
|
|
48
|
+
export declare function inspectDestinationObject(destination: ProviderDestinationConfig, objectKey: string): Promise<{
|
|
49
|
+
size: number | undefined;
|
|
50
|
+
etag: string | undefined;
|
|
51
|
+
versionId: string | undefined;
|
|
52
|
+
metadata: Record<string, string>;
|
|
53
|
+
}>;
|
|
30
54
|
export declare function signDestinationRoute(input: {
|
|
31
55
|
chunk: ChunkSigningPlanItem;
|
|
32
56
|
target: ChunkDestinationSigningTarget;
|
|
@@ -45,10 +69,14 @@ export declare function signDestinationRoute(input: {
|
|
|
45
69
|
maxPartNumber?: number;
|
|
46
70
|
finalObjectMetadata?: Record<string, string>;
|
|
47
71
|
multipartGroupId?: string;
|
|
72
|
+
/** Pre-issued destination URL, used by providers that sign their own upload targets. */
|
|
73
|
+
destUrl?: string;
|
|
48
74
|
fetchImpl?: typeof fetch;
|
|
49
75
|
}): Promise<SignedChunkRoute>;
|
|
50
76
|
export declare function signSourceReadRange(input: {
|
|
51
77
|
source: ProviderSourceConfig;
|
|
78
|
+
ifMatch?: string;
|
|
79
|
+
versionId?: string;
|
|
52
80
|
offset: number;
|
|
53
81
|
length: number;
|
|
54
82
|
expiresIn: number;
|
|
@@ -68,7 +96,17 @@ export declare function signDestinationReadRange(input: {
|
|
|
68
96
|
url: string;
|
|
69
97
|
headers: Record<string, string>;
|
|
70
98
|
}>;
|
|
99
|
+
export declare function signDestinationUrl(input: {
|
|
100
|
+
contentMd5?: string;
|
|
101
|
+
destination: ProviderDestinationConfig;
|
|
102
|
+
objectKey: string;
|
|
103
|
+
uploadId?: string;
|
|
104
|
+
partNumber?: number;
|
|
105
|
+
expiresIn: number;
|
|
106
|
+
fetchImpl?: typeof fetch;
|
|
107
|
+
}): Promise<string>;
|
|
71
108
|
export declare function isS3CompatibleProvider(config: ProviderSourceConfig | ProviderDestinationConfig): config is AnyS3CompatibleProviderConfig;
|
|
72
109
|
export declare function s3CompatibleEndpoint(source: AnyS3CompatibleProviderConfig): string | undefined;
|
|
73
110
|
export declare function s3CompatibleRegion(source: AnyS3CompatibleProviderConfig): string;
|
|
74
111
|
export declare function s3CompatibleForcePathStyle(source: AnyS3CompatibleProviderConfig, endpoint?: string | undefined): boolean | undefined;
|
|
112
|
+
export declare function isHuggingFaceProvider(config: ProviderSourceConfig | ProviderDestinationConfig): config is HuggingFaceProviderConfig;
|