@plasius/gpu-model-runtime 0.2.5 → 0.3.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/dist/index.d.ts CHANGED
@@ -1,14 +1,12 @@
1
+ import { GpuModelDiagnosticMode, GpuModelDocument, ModelSource as ModelSource$1, ModelProvenance as ModelProvenance$1 } from '@plasius/gpu-model-core';
2
+ export { GpuModelConversionDiagnostic, GpuModelDiagnosticMode, GpuModelDocument } from '@plasius/gpu-model-core';
1
3
  import { ModelAssetRef, ModelLodLevel } from '@plasius/asset-contracts';
2
4
 
3
5
  /** Default ceiling per entrypoint or related resource: 64 MiB. */
4
6
  declare const DEFAULT_MODEL_MAX_BYTES: number;
5
- type ModelSourceKind = "file-path" | "url" | "blob-storage-url" | "blob" | "array-buffer" | "uint8-array" | "stream";
6
- type ModelProvenance = Readonly<{
7
- sourceId?: string;
8
- sourceSystem?: string;
9
- acquiredAt?: string;
10
- licenseHint?: string;
11
- }>;
7
+ /** Canonical source and policy contracts stay owned by the published core. */
8
+ type ModelSourceKind = ModelSource$1["kind"];
9
+ type ModelProvenance = ModelProvenance$1;
12
10
  type ModelResourceInput = Readonly<{
13
11
  bytes: ArrayBuffer | Blob | Uint8Array;
14
12
  contentType?: string;
@@ -19,56 +17,9 @@ type ModelPackage = Readonly<{
19
17
  baseUrl?: string;
20
18
  resources?: Readonly<Record<string, ModelResourceInput>>;
21
19
  }>;
22
- type ModelSource = Readonly<{
23
- kind: "file-path";
24
- path: string;
25
- mimeTypeHint?: string;
26
- fileNameHint?: string;
27
- package?: ModelPackage;
28
- provenance?: ModelProvenance;
29
- }> | Readonly<{
30
- kind: "url";
31
- url: string;
32
- headers?: Readonly<Record<string, string>>;
33
- mimeTypeHint?: string;
34
- package?: ModelPackage;
35
- provenance?: ModelProvenance;
36
- }> | Readonly<{
37
- kind: "blob-storage-url";
38
- url: string;
39
- credentialMode?: "anonymous" | "signed" | "runtime-resolved";
40
- mimeTypeHint?: string;
41
- package?: ModelPackage;
42
- provenance?: ModelProvenance;
43
- }> | Readonly<{
44
- kind: "blob";
45
- blob: Blob;
46
- fileNameHint?: string;
47
- mimeTypeHint?: string;
48
- package?: ModelPackage;
49
- provenance?: ModelProvenance;
50
- }> | Readonly<{
51
- kind: "array-buffer";
52
- bytes: ArrayBuffer;
53
- fileNameHint?: string;
54
- mimeTypeHint?: string;
55
- package?: ModelPackage;
56
- provenance?: ModelProvenance;
57
- }> | Readonly<{
58
- kind: "uint8-array";
59
- bytes: Uint8Array;
60
- fileNameHint?: string;
61
- mimeTypeHint?: string;
62
- package?: ModelPackage;
63
- provenance?: ModelProvenance;
64
- }> | Readonly<{
65
- kind: "stream";
66
- stream: ReadableStream<Uint8Array> | AsyncIterable<Uint8Array>;
67
- fileNameHint?: string;
68
- mimeTypeHint?: string;
69
- byteLengthHint?: number;
20
+ /** Runtime acquisition extends canonical IO with package-resource resolution. */
21
+ type ModelSource = ModelSource$1 & Readonly<{
70
22
  package?: ModelPackage;
71
- provenance?: ModelProvenance;
72
23
  }>;
73
24
  type ResourceReference = Readonly<{
74
25
  path: string;
@@ -134,16 +85,16 @@ type SniffInput = Readonly<{
134
85
  }>;
135
86
  type AdapterLoadContext = Readonly<{
136
87
  signal: AbortSignal;
137
- mode: "strict" | "tolerant" | "forensic";
88
+ mode: GpuModelDiagnosticMode;
138
89
  execution: "main" | "worker";
139
90
  adapterOptions: unknown;
140
91
  }>;
141
- type AdapterLoadResult<TCanonicalModel, TRendererReady = unknown> = Readonly<{
92
+ type AdapterLoadResult<TCanonicalModel = GpuModelDocument, TRendererReady = unknown> = Readonly<{
142
93
  canonicalModel: TCanonicalModel;
143
94
  rendererReady?: TRendererReady;
144
95
  diagnostics?: readonly unknown[];
145
96
  }>;
146
- interface ModelAdapter<TCanonicalModel, TRendererReady = unknown> {
97
+ interface ModelAdapter<TCanonicalModel = GpuModelDocument, TRendererReady = unknown> {
147
98
  readonly formatId: string;
148
99
  readonly supportsWorker?: boolean;
149
100
  sniff(input: SniffInput): number | boolean;
@@ -175,7 +126,7 @@ type ModelCache = Readonly<{
175
126
  }>;
176
127
  type LoadModelOptions = Readonly<{
177
128
  signal?: AbortSignal;
178
- mode?: "strict" | "tolerant" | "forensic";
129
+ mode?: GpuModelDiagnosticMode;
179
130
  adapterOptions?: unknown;
180
131
  formatId?: string;
181
132
  fetchPolicy?: FetchPolicy;
@@ -185,7 +136,7 @@ type LoadModelOptions = Readonly<{
185
136
  mode?: "auto" | "never" | "always";
186
137
  }>;
187
138
  }>;
188
- type ModelLoadResult<TCanonicalModel, TRendererReady = unknown> = Readonly<{
139
+ type ModelLoadResult<TCanonicalModel = GpuModelDocument, TRendererReady = unknown> = Readonly<{
189
140
  canonicalModel: TCanonicalModel;
190
141
  rendererReady?: TRendererReady;
191
142
  diagnostics?: readonly unknown[];
package/dist/index.js CHANGED
@@ -33,6 +33,10 @@ function createModelCacheKey(contentHash, formatId, adapterOptions) {
33
33
  var DEFAULT_MODEL_MAX_BYTES = 64 * 1024 * 1024;
34
34
 
35
35
  // src/bytes.ts
36
+ var typedArrayKind = Object.getOwnPropertyDescriptor(
37
+ Object.getPrototypeOf(Uint8Array.prototype),
38
+ Symbol.toStringTag
39
+ )?.get;
36
40
  var ModelResourceTooLargeError = class extends Error {
37
41
  code = "MODEL_RESOURCE_TOO_LARGE";
38
42
  constructor() {
@@ -109,7 +113,7 @@ async function readBoundedStream(source, maxBytes, signal) {
109
113
  const next = await abortable(reader ? reader.read() : iterator.next(), signal);
110
114
  checkAbort(signal);
111
115
  if (next.done) break;
112
- if (!(next.value instanceof Uint8Array)) throw new TypeError("Model stream chunks must be Uint8Array");
116
+ if (!ArrayBuffer.isView(next.value) || typedArrayKind?.call(next.value) !== "Uint8Array") throw new TypeError("Model stream chunks must be Uint8Array");
113
117
  const chunk = next.value;
114
118
  if (chunk.byteLength > maxBytes - length) throw new ModelResourceTooLargeError();
115
119
  const required = length + chunk.byteLength;
@@ -241,12 +245,15 @@ async function verifyIntegrity(bytes, integrity, hashBytes) {
241
245
  if (!integrity) {
242
246
  return;
243
247
  }
244
- const expected = integrity.replace(/^sha256-/iu, "").toLowerCase();
245
- const actualHex = (await hashBytes(bytes)).toLowerCase();
246
- const actualBase64 = bytesToBase64(await digestBytes(bytes, hashBytes));
247
- if (expected !== actualHex && expected !== actualBase64) {
248
- throw new NonRetryableResourceError("Model resource integrity check failed");
248
+ const expected = integrity.replace(/^sha256-/iu, "");
249
+ if (/^[0-9a-f]{64}$/iu.test(expected)) {
250
+ const actualHex = await hashBytes(bytes);
251
+ if (expected.toLowerCase() === actualHex.toLowerCase()) return;
252
+ } else if (/^[A-Za-z0-9+/]{43}=$/u.test(expected)) {
253
+ const actualBase64 = bytesToBase64(await digestBytes(bytes, hashBytes));
254
+ if (expected === actualBase64) return;
249
255
  }
256
+ throw new NonRetryableResourceError("Model resource integrity check failed");
250
257
  }
251
258
  async function digestBytes(bytes, hashBytes) {
252
259
  if (globalThis.crypto?.subtle) {
@@ -336,7 +343,7 @@ var PackageResourceResolver = class {
336
343
  const memoryResource = this.memoryResources[memoryKey] ?? this.memoryResources[`./${memoryKey}`];
337
344
  if (memoryResource) {
338
345
  const bytes = await boundedBytes(memoryResource.bytes, maxBytes, controller.signal);
339
- await abortable(verifyResourceIntegrity(bytes, requested.integrity ?? memoryResource.integrity, this.hashBytes), controller.signal);
346
+ await abortable(verifyIntegrity(bytes, requested.integrity ?? memoryResource.integrity, this.hashBytes), controller.signal);
340
347
  return {
341
348
  path: memoryKey,
342
349
  bytes,
@@ -424,7 +431,23 @@ var ModelRuntime = class {
424
431
  fileName: input.fileName,
425
432
  sourceKind: source.kind
426
433
  }, options.formatId);
427
- const cacheKey = createModelCacheKey(input.contentHash, adapter.formatId, { adapterOptions: options.adapterOptions ?? null, maxBytes: byteLimit(options.fetchPolicy?.maxBytes) });
434
+ const workerMode = options.worker?.mode ?? "auto";
435
+ const canUseWorker = workerMode !== "never" && Boolean(this.workerDispatcher) && Boolean(adapter.supportsWorker);
436
+ if (workerMode === "always" && !canUseWorker) {
437
+ throw new Error(`Adapter ${adapter.formatId} does not support the requested worker execution`);
438
+ }
439
+ const context = {
440
+ signal: options.signal ?? new AbortController().signal,
441
+ mode: options.mode ?? "strict",
442
+ execution: canUseWorker ? "worker" : "main",
443
+ adapterOptions: options.adapterOptions ?? null
444
+ };
445
+ const cacheKey = createModelCacheKey(input.contentHash, adapter.formatId, {
446
+ adapterOptions: context.adapterOptions,
447
+ maxBytes: byteLimit(options.fetchPolicy?.maxBytes),
448
+ mode: context.mode,
449
+ execution: context.execution
450
+ });
428
451
  const useCache = options.useCache ?? true;
429
452
  if (useCache) {
430
453
  const cached = await this.cache.get(cacheKey);
@@ -432,13 +455,7 @@ var ModelRuntime = class {
432
455
  return { ...cached, fromCache: true };
433
456
  }
434
457
  }
435
- const context = {
436
- signal: options.signal ?? new AbortController().signal,
437
- mode: options.mode ?? "strict",
438
- execution: "main",
439
- adapterOptions: options.adapterOptions ?? null
440
- };
441
- const adapterResult = await this.loadWithExecution(adapter, input, context, options);
458
+ const adapterResult = await this.loadWithExecution(adapter, input, context);
442
459
  const result = {
443
460
  ...adapterResult,
444
461
  contentHash: input.contentHash,
@@ -453,13 +470,8 @@ var ModelRuntime = class {
453
470
  }
454
471
  return result;
455
472
  }
456
- async loadWithExecution(adapter, input, context, options) {
457
- const workerMode = options.worker?.mode ?? "auto";
458
- const canUseWorker = workerMode !== "never" && Boolean(this.workerDispatcher) && Boolean(adapter.supportsWorker);
459
- if (workerMode === "always" && !canUseWorker) {
460
- throw new Error(`Adapter ${adapter.formatId} does not support the requested worker execution`);
461
- }
462
- if (canUseWorker && this.workerDispatcher) {
473
+ async loadWithExecution(adapter, input, context) {
474
+ if (context.execution === "worker" && this.workerDispatcher) {
463
475
  const workerRequest = {
464
476
  formatId: adapter.formatId,
465
477
  input,
@@ -565,14 +577,6 @@ async function sourceToBytes(source, maxBytes, signal) {
565
577
  if (source.byteLengthHint !== void 0) checkSize(source.byteLengthHint, maxBytes);
566
578
  return readBoundedStream(source.stream, maxBytes, signal);
567
579
  }
568
- async function verifyResourceIntegrity(bytes, integrity, hashBytes) {
569
- if (!integrity) return;
570
- const expected = integrity.replace(/^sha256-/iu, "").toLowerCase();
571
- const actual = (await hashBytes(bytes)).toLowerCase();
572
- if (actual !== expected) {
573
- throw new Error("Model resource integrity check failed");
574
- }
575
- }
576
580
  var defaultFetch = (input, init) => {
577
581
  if (!globalThis.fetch) {
578
582
  throw new Error("URL sources require fetch or an injected fetch dependency");