@plasius/gpu-model-runtime 0.2.0 → 0.2.1
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 +77 -1
- package/dist/index.cjs +236 -136
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +19 -2
- package/dist/index.d.ts +19 -2
- package/dist/index.js +234 -136
- package/dist/index.js.map +1 -1
- package/package.json +15 -11
package/README.md
CHANGED
|
@@ -25,7 +25,7 @@ is released.
|
|
|
25
25
|
statuses, abort signals, optional byte ranges, response content types, and
|
|
26
26
|
SHA-256 integrity checks.
|
|
27
27
|
- `MemoryModelCache` keys loaded output by content hash, adapter format, and
|
|
28
|
-
stable adapter options. Call `runtime.invalidate()` to clear all entries or
|
|
28
|
+
stable adapter options and acquisition byte ceiling. Call `runtime.invalidate()` to clear all entries or
|
|
29
29
|
entries for one content hash.
|
|
30
30
|
- Worker execution is opt-in through a `WorkerDispatcher`; consumers that do
|
|
31
31
|
not provide one stay on the main execution path.
|
|
@@ -50,6 +50,71 @@ result.canonicalModel;
|
|
|
50
50
|
result.rendererReady;
|
|
51
51
|
```
|
|
52
52
|
|
|
53
|
+
## Bounded source acquisition
|
|
54
|
+
|
|
55
|
+
Every entrypoint and related resource has a default ceiling of 64 MiB
|
|
56
|
+
(`DEFAULT_MODEL_MAX_BYTES`). `fetchPolicy.maxBytes` applies to URLs, memory,
|
|
57
|
+
Blobs, streams, and injected file reads. Opt into a larger finite positive safe
|
|
58
|
+
integer only after budgeting for that workload:
|
|
59
|
+
|
|
60
|
+
```ts
|
|
61
|
+
await runtime.loadModel(source, {
|
|
62
|
+
fetchPolicy: { maxBytes: 128 * 1024 * 1024, timeoutMs: 30_000 },
|
|
63
|
+
signal: controller.signal,
|
|
64
|
+
});
|
|
65
|
+
// In an adapter, tighten the inherited ceiling for one texture:
|
|
66
|
+
await input.resourceResolver.resolve("textures/albedo.png", {
|
|
67
|
+
maxBytes: 8 * 1024 * 1024,
|
|
68
|
+
signal: context.signal,
|
|
69
|
+
});
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
A per-resolution limit can tighten the inherited ceiling, never enlarge it.
|
|
73
|
+
Known lengths are checked before reading/copying, and actual streamed bytes are
|
|
74
|
+
checked before retention even when a server omits/misstates Content-Length or
|
|
75
|
+
ignores Range. `byteLengthHint` can reject an oversized stream early; an
|
|
76
|
+
underestimate never disables actual-byte checks. Hashes still cover the admitted
|
|
77
|
+
bytes, including a returned range. Limits apply to decoded Fetch body bytes;
|
|
78
|
+
compressed Content-Length is only an additional early check.
|
|
79
|
+
|
|
80
|
+
Oversize throws `ModelResourceTooLargeError` with code
|
|
81
|
+
`MODEL_RESOURCE_TOO_LARGE` and a fixed message containing no source identifiers.
|
|
82
|
+
It is not retried. Cancellation and acquisition deadlines stop pending reads,
|
|
83
|
+
cancel/release readers, and request iterator cleanup. An uncooperative producer
|
|
84
|
+
cannot delay the rejection, although its own background work cannot be forcibly
|
|
85
|
+
stopped. File-provider callbacks now receive a third `{ maxBytes }` argument;
|
|
86
|
+
they must enforce it incrementally during their own I/O and honor the signal.
|
|
87
|
+
The runtime checks returned file bytes before hashing or adapter use. Existing
|
|
88
|
+
two-argument providers remain callable but need review for pre-allocation limits.
|
|
89
|
+
|
|
90
|
+
The 30-second default `timeoutMs` now covers local/stream acquisition too;
|
|
91
|
+
for ModelRuntime it includes entrypoint fetching, retries and hashing. Each
|
|
92
|
+
related-resource resolution gets its own deadline and inherits caller abort.
|
|
93
|
+
Direct `fetchResource` retains a deadline per attempt, including retry delay.
|
|
94
|
+
Timeouts must be positive integers no larger than 2,147,483,647 milliseconds.
|
|
95
|
+
|
|
96
|
+
The ceiling is per resource, not a total process/model budget. Stream storage
|
|
97
|
+
grows geometrically with no per-chunk metadata list; live runtime copy storage
|
|
98
|
+
is at most twice the configured ceiling (excluding producer/network buffers and
|
|
99
|
+
hashing). Many tiny/empty chunks yield to the event loop periodically so timers
|
|
100
|
+
can cancel work. Adapters still own total resource count/bytes, decoded image or
|
|
101
|
+
mesh budgets, concurrency and format validation. ModelResidencyManager separately
|
|
102
|
+
bounds resident CPU/GPU resources. Cache identity includes maxBytes so a model
|
|
103
|
+
loaded with a larger ceiling cannot bypass a later stricter request.
|
|
104
|
+
|
|
105
|
+
This behavior requires a **minor release in the current 0.x series**: previously
|
|
106
|
+
accepted resources above 64 MiB require an explicit larger budget. The release
|
|
107
|
+
pipeline allocates the version; consumers must wait for a verified npm release.
|
|
108
|
+
Disabling `gpu.model.conversion.enabled` at the consumer prevents conversion and
|
|
109
|
+
restores its tested fallback. Byte limits remain enforced whenever this runtime
|
|
110
|
+
is called; there is no safety-check bypass flag.
|
|
111
|
+
|
|
112
|
+
The implementation uses browser Web Streams, Blob, AbortController, timers and
|
|
113
|
+
Web Crypto (or injected hashing), with no filesystem dependency. Node 24 tests
|
|
114
|
+
exercise those APIs, streaming/range/integrity semantics and both ESM/CJS builds;
|
|
115
|
+
real-browser integration remains a consumer validation responsibility. See
|
|
116
|
+
[ADR-0008](docs/adrs/adr-0008-bounded-source-acquisition.md).
|
|
117
|
+
|
|
53
118
|
## Model residency
|
|
54
119
|
|
|
55
120
|
`ModelResidencyManager` owns the lifetime of promoted runtime models shared by
|
|
@@ -146,3 +211,14 @@ npm pack --dry-run
|
|
|
146
211
|
## License
|
|
147
212
|
|
|
148
213
|
Apache-2.0. See LICENSE, SECURITY.md, and the files under legal/.
|
|
214
|
+
|
|
215
|
+
<!-- BEGIN PLASIUS RELEASE INTEGRITY -->
|
|
216
|
+
## Release integrity
|
|
217
|
+
|
|
218
|
+
CI keeps the administrative contributor registry outside Git and npm package
|
|
219
|
+
artifacts using exact, case-normalised path checks. CI runs on explicit
|
|
220
|
+
GitHub-hosted runners. Publication uses the GitHub-hosted `production` job with
|
|
221
|
+
Node 24 and a pinned npm 11.6.2 client. It is token-free and proceeds only while the prepared SHA
|
|
222
|
+
is the exact `main` head after successful push-triggered CI. Do not dispatch CD
|
|
223
|
+
until the npm trusted-publisher binding is verified.
|
|
224
|
+
<!-- END PLASIUS RELEASE INTEGRITY -->
|
package/dist/index.cjs
CHANGED
|
@@ -21,9 +21,11 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
21
21
|
var index_exports = {};
|
|
22
22
|
__export(index_exports, {
|
|
23
23
|
AdapterRegistry: () => AdapterRegistry,
|
|
24
|
+
DEFAULT_MODEL_MAX_BYTES: () => DEFAULT_MODEL_MAX_BYTES,
|
|
24
25
|
MemoryModelCache: () => MemoryModelCache,
|
|
25
26
|
ModelResidencyError: () => ModelResidencyError,
|
|
26
27
|
ModelResidencyManager: () => ModelResidencyManager,
|
|
28
|
+
ModelResourceTooLargeError: () => ModelResourceTooLargeError,
|
|
27
29
|
ModelRuntime: () => ModelRuntime,
|
|
28
30
|
PackageResourceResolver: () => PackageResourceResolver,
|
|
29
31
|
createModelCacheKey: () => createModelCacheKey,
|
|
@@ -68,8 +70,117 @@ function createModelCacheKey(contentHash, formatId, adapterOptions) {
|
|
|
68
70
|
return `${formatId}:${contentHash}:${JSON.stringify(stableValue(adapterOptions ?? null))}`;
|
|
69
71
|
}
|
|
70
72
|
|
|
73
|
+
// src/types.ts
|
|
74
|
+
var DEFAULT_MODEL_MAX_BYTES = 64 * 1024 * 1024;
|
|
75
|
+
|
|
76
|
+
// src/bytes.ts
|
|
77
|
+
var ModelResourceTooLargeError = class extends Error {
|
|
78
|
+
code = "MODEL_RESOURCE_TOO_LARGE";
|
|
79
|
+
constructor() {
|
|
80
|
+
super("Model resource exceeds maxBytes");
|
|
81
|
+
this.name = "ModelResourceTooLargeError";
|
|
82
|
+
}
|
|
83
|
+
};
|
|
84
|
+
function byteLimit(value = DEFAULT_MODEL_MAX_BYTES) {
|
|
85
|
+
if (!Number.isSafeInteger(value) || value <= 0) throw new RangeError("maxBytes must be a positive safe integer");
|
|
86
|
+
return value;
|
|
87
|
+
}
|
|
88
|
+
function checkSize(size, maxBytes) {
|
|
89
|
+
if (size > maxBytes) throw new ModelResourceTooLargeError();
|
|
90
|
+
}
|
|
91
|
+
function checkAbort(signal) {
|
|
92
|
+
if (signal?.aborted) throw new DOMException("The model load was aborted", "AbortError");
|
|
93
|
+
}
|
|
94
|
+
function acquisitionController(signal, timeoutMs = 3e4, parent) {
|
|
95
|
+
if (!Number.isSafeInteger(timeoutMs) || timeoutMs <= 0 || timeoutMs > 2147483647) {
|
|
96
|
+
throw new RangeError("timeoutMs must be a positive timer-safe integer");
|
|
97
|
+
}
|
|
98
|
+
const controller = new AbortController();
|
|
99
|
+
const abort = () => controller.abort();
|
|
100
|
+
signal?.addEventListener("abort", abort, { once: true });
|
|
101
|
+
parent?.addEventListener("abort", abort, { once: true });
|
|
102
|
+
if (signal?.aborted || parent?.aborted) controller.abort();
|
|
103
|
+
const timer = setTimeout(abort, timeoutMs);
|
|
104
|
+
return {
|
|
105
|
+
signal: controller.signal,
|
|
106
|
+
dispose() {
|
|
107
|
+
clearTimeout(timer);
|
|
108
|
+
signal?.removeEventListener("abort", abort);
|
|
109
|
+
parent?.removeEventListener("abort", abort);
|
|
110
|
+
}
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
async function abortable(work, signal) {
|
|
114
|
+
return new Promise((resolve, reject) => {
|
|
115
|
+
const abort = () => {
|
|
116
|
+
signal.removeEventListener("abort", abort);
|
|
117
|
+
reject(new DOMException("The model load was aborted", "AbortError"));
|
|
118
|
+
};
|
|
119
|
+
signal.addEventListener("abort", abort, { once: true });
|
|
120
|
+
if (signal.aborted) abort();
|
|
121
|
+
work.then(
|
|
122
|
+
(value) => {
|
|
123
|
+
signal.removeEventListener("abort", abort);
|
|
124
|
+
resolve(value);
|
|
125
|
+
},
|
|
126
|
+
(error) => {
|
|
127
|
+
signal.removeEventListener("abort", abort);
|
|
128
|
+
reject(error);
|
|
129
|
+
}
|
|
130
|
+
);
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
function cleanup(work) {
|
|
134
|
+
try {
|
|
135
|
+
void Promise.resolve(work()).catch(() => {
|
|
136
|
+
});
|
|
137
|
+
} catch {
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
async function readBoundedStream(source, maxBytes, signal) {
|
|
141
|
+
let bytes = new Uint8Array(0);
|
|
142
|
+
let length = 0;
|
|
143
|
+
let chunks = 0;
|
|
144
|
+
const reader = "getReader" in source ? source.getReader() : void 0;
|
|
145
|
+
const iterator = reader ? void 0 : source[Symbol.asyncIterator]();
|
|
146
|
+
try {
|
|
147
|
+
while (true) {
|
|
148
|
+
checkAbort(signal);
|
|
149
|
+
if (++chunks % 256 === 0) await abortable(new Promise((resolve) => setTimeout(resolve, 0)), signal);
|
|
150
|
+
const next = await abortable(reader ? reader.read() : iterator.next(), signal);
|
|
151
|
+
checkAbort(signal);
|
|
152
|
+
if (next.done) break;
|
|
153
|
+
if (!(next.value instanceof Uint8Array)) throw new TypeError("Model stream chunks must be Uint8Array");
|
|
154
|
+
const chunk = next.value;
|
|
155
|
+
if (chunk.byteLength > maxBytes - length) throw new ModelResourceTooLargeError();
|
|
156
|
+
const required = length + chunk.byteLength;
|
|
157
|
+
if (required > bytes.length) {
|
|
158
|
+
const grown = new Uint8Array(Math.min(maxBytes, Math.max(required, bytes.length * 2, 4096)));
|
|
159
|
+
grown.set(bytes.subarray(0, length));
|
|
160
|
+
bytes = grown;
|
|
161
|
+
}
|
|
162
|
+
bytes.set(chunk, length);
|
|
163
|
+
length = required;
|
|
164
|
+
}
|
|
165
|
+
return bytes.length === length ? bytes : bytes.slice(0, length);
|
|
166
|
+
} catch (error) {
|
|
167
|
+
if (reader) cleanup(() => reader.cancel());
|
|
168
|
+
else cleanup(() => iterator?.return?.());
|
|
169
|
+
throw error;
|
|
170
|
+
} finally {
|
|
171
|
+
reader?.releaseLock();
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
async function boundedBytes(input, maxBytes, signal) {
|
|
175
|
+
checkAbort(signal);
|
|
176
|
+
checkSize(input instanceof Blob ? input.size : input.byteLength, maxBytes);
|
|
177
|
+
if (input instanceof Blob) return readBoundedStream(input.stream(), maxBytes, signal);
|
|
178
|
+
return new Uint8Array(input);
|
|
179
|
+
}
|
|
180
|
+
|
|
71
181
|
// src/fetch.ts
|
|
72
182
|
var DEFAULT_POLICY = {
|
|
183
|
+
maxBytes: DEFAULT_MODEL_MAX_BYTES,
|
|
73
184
|
maxAttempts: 3,
|
|
74
185
|
baseDelayMs: 100,
|
|
75
186
|
maxDelayMs: 1e3,
|
|
@@ -100,6 +211,7 @@ async function sha256Hex(bytes) {
|
|
|
100
211
|
}
|
|
101
212
|
async function fetchResource(url, options) {
|
|
102
213
|
const policy = { ...DEFAULT_POLICY, ...options.policy };
|
|
214
|
+
const maxBytes = Math.min(byteLimit(policy.maxBytes), byteLimit(options.resolveOptions?.maxBytes ?? policy.maxBytes));
|
|
103
215
|
const headers = new Headers(options.headers);
|
|
104
216
|
if (options.resolveOptions?.range) {
|
|
105
217
|
const { start, end } = options.resolveOptions.range;
|
|
@@ -107,13 +219,21 @@ async function fetchResource(url, options) {
|
|
|
107
219
|
}
|
|
108
220
|
let lastError;
|
|
109
221
|
for (let attempt = 1; attempt <= policy.maxAttempts; attempt += 1) {
|
|
110
|
-
const controller =
|
|
222
|
+
const controller = acquisitionController(options.resolveOptions?.signal, policy.timeoutMs);
|
|
111
223
|
try {
|
|
112
|
-
|
|
224
|
+
checkAbort(controller.signal);
|
|
225
|
+
const response = await abortable(options.fetch(url, {
|
|
113
226
|
headers,
|
|
114
227
|
signal: controller.signal
|
|
115
|
-
})
|
|
228
|
+
}).then((response2) => {
|
|
229
|
+
if (controller.signal.aborted) {
|
|
230
|
+
cleanup(() => response2.body?.cancel());
|
|
231
|
+
checkAbort(controller.signal);
|
|
232
|
+
}
|
|
233
|
+
return response2;
|
|
234
|
+
}), controller.signal);
|
|
116
235
|
if (!response.ok) {
|
|
236
|
+
cleanup(() => response.body?.cancel());
|
|
117
237
|
if (!RETRYABLE_STATUSES.has(response.status)) {
|
|
118
238
|
throw new NonRetryableResourceError(`Model resource request failed with HTTP ${response.status}`);
|
|
119
239
|
}
|
|
@@ -121,11 +241,20 @@ async function fetchResource(url, options) {
|
|
|
121
241
|
throw new NonRetryableResourceError(`Model resource request failed with HTTP ${response.status}`);
|
|
122
242
|
}
|
|
123
243
|
lastError = new Error(`Retryable model resource response: HTTP ${response.status}`);
|
|
124
|
-
await waitForRetry(options.sleep, attempt, policy,
|
|
244
|
+
await waitForRetry(options.sleep, attempt, policy, controller.signal);
|
|
125
245
|
continue;
|
|
126
246
|
}
|
|
127
|
-
|
|
128
|
-
|
|
247
|
+
let bytes;
|
|
248
|
+
try {
|
|
249
|
+
const declared = response.headers.get("content-length");
|
|
250
|
+
if (declared !== null && /^\d+$/u.test(declared)) checkSize(Number(declared), maxBytes);
|
|
251
|
+
bytes = response.body ? await readBoundedStream(response.body, maxBytes, controller.signal) : new Uint8Array(0);
|
|
252
|
+
checkAbort(controller.signal);
|
|
253
|
+
} catch (error) {
|
|
254
|
+
if (!response.body?.locked) cleanup(() => response.body?.cancel());
|
|
255
|
+
throw error;
|
|
256
|
+
}
|
|
257
|
+
await abortable(verifyIntegrity(bytes, options.integrity, options.hashBytes), controller.signal);
|
|
129
258
|
return {
|
|
130
259
|
bytes,
|
|
131
260
|
contentType: response.headers.get("content-type") ?? void 0,
|
|
@@ -135,14 +264,14 @@ async function fetchResource(url, options) {
|
|
|
135
264
|
if (isAbortError(error) || options.resolveOptions?.signal?.aborted) {
|
|
136
265
|
throw createAbortError();
|
|
137
266
|
}
|
|
138
|
-
if (error instanceof NonRetryableResourceError) {
|
|
267
|
+
if (error instanceof NonRetryableResourceError || error instanceof ModelResourceTooLargeError) {
|
|
139
268
|
throw error;
|
|
140
269
|
}
|
|
141
270
|
lastError = error;
|
|
142
271
|
if (attempt === policy.maxAttempts) {
|
|
143
272
|
throw error;
|
|
144
273
|
}
|
|
145
|
-
await waitForRetry(options.sleep, attempt, policy,
|
|
274
|
+
await waitForRetry(options.sleep, attempt, policy, controller.signal);
|
|
146
275
|
} finally {
|
|
147
276
|
controller.dispose();
|
|
148
277
|
}
|
|
@@ -179,20 +308,7 @@ function bytesToBase64(bytes) {
|
|
|
179
308
|
}
|
|
180
309
|
async function waitForRetry(sleep, attempt, policy, signal) {
|
|
181
310
|
const delay = Math.min(policy.maxDelayMs, policy.baseDelayMs * 2 ** (attempt - 1));
|
|
182
|
-
await sleep(delay, signal);
|
|
183
|
-
}
|
|
184
|
-
function createRequestController(signal, timeoutMs) {
|
|
185
|
-
const controller = new AbortController();
|
|
186
|
-
const onAbort = () => controller.abort();
|
|
187
|
-
signal?.addEventListener("abort", onAbort, { once: true });
|
|
188
|
-
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
189
|
-
return {
|
|
190
|
-
signal: controller.signal,
|
|
191
|
-
dispose: () => {
|
|
192
|
-
clearTimeout(timer);
|
|
193
|
-
signal?.removeEventListener("abort", onAbort);
|
|
194
|
-
}
|
|
195
|
-
};
|
|
311
|
+
await abortable(sleep(delay, signal), signal);
|
|
196
312
|
}
|
|
197
313
|
function createAbortError() {
|
|
198
314
|
return new DOMException("The model load was aborted", "AbortError");
|
|
@@ -228,6 +344,7 @@ var AdapterRegistry = class {
|
|
|
228
344
|
}
|
|
229
345
|
};
|
|
230
346
|
var PackageResourceResolver = class {
|
|
347
|
+
parentSignal;
|
|
231
348
|
memoryResources;
|
|
232
349
|
baseUrl;
|
|
233
350
|
sourceKind;
|
|
@@ -238,48 +355,58 @@ var PackageResourceResolver = class {
|
|
|
238
355
|
headers;
|
|
239
356
|
blobStorageUrlResolver;
|
|
240
357
|
constructor(options) {
|
|
358
|
+
this.parentSignal = options.signal;
|
|
241
359
|
this.sourceKind = options.sourceKind;
|
|
242
360
|
this.baseUrl = options.package?.baseUrl ?? options.baseUrl;
|
|
243
361
|
this.memoryResources = options.package?.resources ?? {};
|
|
244
362
|
this.fetch = options.fetch;
|
|
245
363
|
this.hashBytes = options.hashBytes;
|
|
246
364
|
this.sleep = options.sleep;
|
|
247
|
-
this.fetchPolicy = options.fetchPolicy;
|
|
365
|
+
this.fetchPolicy = { ...options.fetchPolicy };
|
|
248
366
|
this.headers = options.headers;
|
|
249
367
|
this.blobStorageUrlResolver = options.blobStorageUrlResolver;
|
|
250
368
|
}
|
|
251
369
|
async resolve(reference, options = {}) {
|
|
252
|
-
const
|
|
253
|
-
const
|
|
254
|
-
const
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
370
|
+
const inheritedLimit = byteLimit(this.fetchPolicy?.maxBytes);
|
|
371
|
+
const maxBytes = Math.min(inheritedLimit, byteLimit(options.maxBytes ?? inheritedLimit));
|
|
372
|
+
const controller = acquisitionController(options.signal, this.fetchPolicy?.timeoutMs, this.parentSignal);
|
|
373
|
+
try {
|
|
374
|
+
checkAbort(controller.signal);
|
|
375
|
+
const requested = typeof reference === "string" ? { path: reference } : reference;
|
|
376
|
+
const memoryKey = normalizePackagePath(requested.path);
|
|
377
|
+
const memoryResource = this.memoryResources[memoryKey] ?? this.memoryResources[`./${memoryKey}`];
|
|
378
|
+
if (memoryResource) {
|
|
379
|
+
const bytes = await boundedBytes(memoryResource.bytes, maxBytes, controller.signal);
|
|
380
|
+
await abortable(verifyResourceIntegrity(bytes, requested.integrity ?? memoryResource.integrity, this.hashBytes), controller.signal);
|
|
381
|
+
return {
|
|
382
|
+
path: memoryKey,
|
|
383
|
+
bytes,
|
|
384
|
+
contentType: requested.contentType ?? memoryResource.contentType,
|
|
385
|
+
contentHash: await abortable(this.hashBytes(bytes), controller.signal),
|
|
386
|
+
rangeSupported: false
|
|
387
|
+
};
|
|
388
|
+
}
|
|
389
|
+
const url = await abortable(this.resolveUrl(requested.path), controller.signal);
|
|
390
|
+
checkAbort(controller.signal);
|
|
391
|
+
const result = await fetchResource(url, {
|
|
392
|
+
fetch: this.fetch,
|
|
393
|
+
hashBytes: this.hashBytes,
|
|
394
|
+
sleep: this.sleep,
|
|
395
|
+
headers: this.headers,
|
|
396
|
+
integrity: requested.integrity,
|
|
397
|
+
policy: this.fetchPolicy,
|
|
398
|
+
resolveOptions: { ...options, maxBytes, signal: controller.signal }
|
|
399
|
+
});
|
|
258
400
|
return {
|
|
259
401
|
path: memoryKey,
|
|
260
|
-
bytes,
|
|
261
|
-
contentType: requested.contentType ??
|
|
262
|
-
contentHash: await this.hashBytes(bytes),
|
|
263
|
-
rangeSupported:
|
|
402
|
+
bytes: result.bytes,
|
|
403
|
+
contentType: requested.contentType ?? result.contentType,
|
|
404
|
+
contentHash: await abortable(this.hashBytes(result.bytes), controller.signal),
|
|
405
|
+
rangeSupported: result.rangeSupported
|
|
264
406
|
};
|
|
407
|
+
} finally {
|
|
408
|
+
controller.dispose();
|
|
265
409
|
}
|
|
266
|
-
const url = await this.resolveUrl(requested.path);
|
|
267
|
-
const result = await fetchResource(url, {
|
|
268
|
-
fetch: this.fetch,
|
|
269
|
-
hashBytes: this.hashBytes,
|
|
270
|
-
sleep: this.sleep,
|
|
271
|
-
headers: this.headers,
|
|
272
|
-
integrity: requested.integrity,
|
|
273
|
-
policy: this.fetchPolicy,
|
|
274
|
-
resolveOptions: options
|
|
275
|
-
});
|
|
276
|
-
return {
|
|
277
|
-
path: memoryKey,
|
|
278
|
-
bytes: result.bytes,
|
|
279
|
-
contentType: requested.contentType ?? result.contentType,
|
|
280
|
-
contentHash: await this.hashBytes(result.bytes),
|
|
281
|
-
rangeSupported: result.rangeSupported
|
|
282
|
-
};
|
|
283
410
|
}
|
|
284
411
|
async resolveUrl(reference) {
|
|
285
412
|
if (/^[a-z][a-z\d+.-]*:/iu.test(reference)) {
|
|
@@ -338,7 +465,7 @@ var ModelRuntime = class {
|
|
|
338
465
|
fileName: input.fileName,
|
|
339
466
|
sourceKind: source.kind
|
|
340
467
|
}, options.formatId);
|
|
341
|
-
const cacheKey = createModelCacheKey(input.contentHash, adapter.formatId, options.adapterOptions);
|
|
468
|
+
const cacheKey = createModelCacheKey(input.contentHash, adapter.formatId, { adapterOptions: options.adapterOptions ?? null, maxBytes: byteLimit(options.fetchPolicy?.maxBytes) });
|
|
342
469
|
const useCache = options.useCache ?? true;
|
|
343
470
|
if (useCache) {
|
|
344
471
|
const cached = await this.cache.get(cacheKey);
|
|
@@ -384,54 +511,64 @@ var ModelRuntime = class {
|
|
|
384
511
|
return adapter.load(input, context);
|
|
385
512
|
}
|
|
386
513
|
async resolveSource(source, options) {
|
|
387
|
-
const
|
|
388
|
-
const
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
514
|
+
const maxBytes = byteLimit(options.fetchPolicy?.maxBytes);
|
|
515
|
+
const controller = acquisitionController(options.signal, options.fetchPolicy?.timeoutMs);
|
|
516
|
+
try {
|
|
517
|
+
checkAbort(controller.signal);
|
|
518
|
+
const fileName = sourceFileName(source);
|
|
519
|
+
const baseUrl = "url" in source ? source.url : void 0;
|
|
520
|
+
const headers = "headers" in source ? source.headers : void 0;
|
|
521
|
+
let bytes;
|
|
522
|
+
let contentType = "mimeTypeHint" in source ? source.mimeTypeHint : void 0;
|
|
523
|
+
let rangeSupported = false;
|
|
524
|
+
if (source.kind === "file-path") {
|
|
525
|
+
if (!this.readFile) {
|
|
526
|
+
throw new Error("file-path sources require a readFile dependency");
|
|
527
|
+
}
|
|
528
|
+
bytes = await abortable(this.readFile(source.path, controller.signal, { maxBytes }), controller.signal);
|
|
529
|
+
checkSize(bytes.byteLength, maxBytes);
|
|
530
|
+
} else if (source.kind === "url" || source.kind === "blob-storage-url") {
|
|
531
|
+
const result = await fetchResource(ensureHttpUrl(source.url), {
|
|
532
|
+
fetch: this.fetch,
|
|
533
|
+
hashBytes: this.hashBytes,
|
|
534
|
+
sleep: this.sleep,
|
|
535
|
+
headers,
|
|
536
|
+
integrity: options.integrity,
|
|
537
|
+
policy: options.fetchPolicy,
|
|
538
|
+
resolveOptions: { signal: controller.signal }
|
|
539
|
+
});
|
|
540
|
+
bytes = result.bytes;
|
|
541
|
+
contentType ??= result.contentType;
|
|
542
|
+
rangeSupported = result.rangeSupported;
|
|
543
|
+
} else {
|
|
544
|
+
bytes = await sourceToBytes(source, maxBytes, controller.signal);
|
|
396
545
|
}
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
const
|
|
546
|
+
checkAbort(controller.signal);
|
|
547
|
+
const contentHash = await abortable(this.hashBytes(bytes), controller.signal);
|
|
548
|
+
const resourceResolver = new PackageResourceResolver({
|
|
549
|
+
sourceKind: source.kind,
|
|
550
|
+
signal: options.signal,
|
|
551
|
+
baseUrl,
|
|
552
|
+
package: source.package,
|
|
400
553
|
fetch: this.fetch,
|
|
401
554
|
hashBytes: this.hashBytes,
|
|
402
555
|
sleep: this.sleep,
|
|
556
|
+
fetchPolicy: options.fetchPolicy,
|
|
403
557
|
headers,
|
|
404
|
-
|
|
405
|
-
policy: options.fetchPolicy,
|
|
406
|
-
resolveOptions: { signal: options.signal }
|
|
558
|
+
blobStorageUrlResolver: this.blobStorageUrlResolver
|
|
407
559
|
});
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
560
|
+
return {
|
|
561
|
+
source,
|
|
562
|
+
bytes,
|
|
563
|
+
contentType,
|
|
564
|
+
fileName,
|
|
565
|
+
contentHash,
|
|
566
|
+
rangeSupported,
|
|
567
|
+
resourceResolver
|
|
568
|
+
};
|
|
569
|
+
} finally {
|
|
570
|
+
controller.dispose();
|
|
413
571
|
}
|
|
414
|
-
const contentHash = await this.hashBytes(bytes);
|
|
415
|
-
const resourceResolver = new PackageResourceResolver({
|
|
416
|
-
sourceKind: source.kind,
|
|
417
|
-
baseUrl,
|
|
418
|
-
package: source.package,
|
|
419
|
-
fetch: this.fetch,
|
|
420
|
-
hashBytes: this.hashBytes,
|
|
421
|
-
sleep: this.sleep,
|
|
422
|
-
fetchPolicy: options.fetchPolicy,
|
|
423
|
-
headers,
|
|
424
|
-
blobStorageUrlResolver: this.blobStorageUrlResolver
|
|
425
|
-
});
|
|
426
|
-
return {
|
|
427
|
-
source,
|
|
428
|
-
bytes,
|
|
429
|
-
contentType,
|
|
430
|
-
fileName,
|
|
431
|
-
contentHash,
|
|
432
|
-
rangeSupported,
|
|
433
|
-
resourceResolver
|
|
434
|
-
};
|
|
435
572
|
}
|
|
436
573
|
};
|
|
437
574
|
function scoreRegistration(registration, input) {
|
|
@@ -463,50 +600,11 @@ function normalizePackagePath(path) {
|
|
|
463
600
|
}
|
|
464
601
|
return segments.join("/");
|
|
465
602
|
}
|
|
466
|
-
async function sourceToBytes(source) {
|
|
467
|
-
if (source.kind === "blob")
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
return new Uint8Array(source.bytes);
|
|
472
|
-
}
|
|
473
|
-
if (source.kind === "uint8-array") {
|
|
474
|
-
return new Uint8Array(source.bytes);
|
|
475
|
-
}
|
|
476
|
-
const chunks = [];
|
|
477
|
-
if (Symbol.asyncIterator in Object(source.stream)) {
|
|
478
|
-
for await (const chunk of source.stream) {
|
|
479
|
-
chunks.push(new Uint8Array(chunk));
|
|
480
|
-
}
|
|
481
|
-
} else {
|
|
482
|
-
const reader = source.stream.getReader();
|
|
483
|
-
try {
|
|
484
|
-
while (true) {
|
|
485
|
-
const next = await reader.read();
|
|
486
|
-
if (next.done) break;
|
|
487
|
-
chunks.push(new Uint8Array(next.value));
|
|
488
|
-
}
|
|
489
|
-
} finally {
|
|
490
|
-
reader.releaseLock();
|
|
491
|
-
}
|
|
492
|
-
}
|
|
493
|
-
const length = chunks.reduce((total, chunk) => total + chunk.byteLength, 0);
|
|
494
|
-
const bytes = new Uint8Array(length);
|
|
495
|
-
let offset = 0;
|
|
496
|
-
for (const chunk of chunks) {
|
|
497
|
-
bytes.set(chunk, offset);
|
|
498
|
-
offset += chunk.byteLength;
|
|
499
|
-
}
|
|
500
|
-
return bytes;
|
|
501
|
-
}
|
|
502
|
-
async function toBytes(input) {
|
|
503
|
-
if (input instanceof Blob) {
|
|
504
|
-
return new Uint8Array(await input.arrayBuffer());
|
|
505
|
-
}
|
|
506
|
-
if (input instanceof Uint8Array) {
|
|
507
|
-
return new Uint8Array(input);
|
|
508
|
-
}
|
|
509
|
-
return new Uint8Array(input);
|
|
603
|
+
async function sourceToBytes(source, maxBytes, signal) {
|
|
604
|
+
if (source.kind === "blob") return boundedBytes(source.blob, maxBytes, signal);
|
|
605
|
+
if (source.kind === "array-buffer" || source.kind === "uint8-array") return boundedBytes(source.bytes, maxBytes, signal);
|
|
606
|
+
if (source.byteLengthHint !== void 0) checkSize(source.byteLengthHint, maxBytes);
|
|
607
|
+
return readBoundedStream(source.stream, maxBytes, signal);
|
|
510
608
|
}
|
|
511
609
|
async function verifyResourceIntegrity(bytes, integrity, hashBytes) {
|
|
512
610
|
if (!integrity) return;
|
|
@@ -1186,9 +1284,11 @@ var packageBootstrap = Object.freeze({
|
|
|
1186
1284
|
// Annotate the CommonJS export names for ESM import in node:
|
|
1187
1285
|
0 && (module.exports = {
|
|
1188
1286
|
AdapterRegistry,
|
|
1287
|
+
DEFAULT_MODEL_MAX_BYTES,
|
|
1189
1288
|
MemoryModelCache,
|
|
1190
1289
|
ModelResidencyError,
|
|
1191
1290
|
ModelResidencyManager,
|
|
1291
|
+
ModelResourceTooLargeError,
|
|
1192
1292
|
ModelRuntime,
|
|
1193
1293
|
PackageResourceResolver,
|
|
1194
1294
|
createModelCacheKey,
|