@nemesis-oss/ollama-sdk 1.16.0 → 1.17.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/CHANGELOG.md CHANGED
@@ -1,5 +1,32 @@
1
1
  # Changelog
2
2
 
3
+ ## [1.17.0] — Streaming blob uploads (`duplex:'half'`, two-pass file upload), typed-array view-safety pins, tracing-header verdicts
4
+
5
+ The October 14 digest ("Raw Model Blob Ingestion Streaming Backpressure, W3C Distributed Tracing Baggage Injection, Defensive Serialization for Typed Arrays") targeted heavy binary asset streaming, operational provenance, and low-level buffer safety. Audited against the codebase first (see [ADR 0032](./docs/adr/0032-streaming-blob-uploads-tracing-verdicts-view-safety.md)): the heap-exhaustion claim was real on the blob path — `createBlobFromFile` bulk-read the whole file (`fs.readFile`) before uploading, and the `ReadableStream` member of `BinaryBody` was a promise the transport couldn't keep (Node 18+ `fetch` rejects stream bodies without `duplex: 'half'`) — though the `/api/create` framing was misdirected (its request body is a small JSON digest map, not a stream). The tracing claim was stale (client-level `headers`, per-request headers, and the middleware seam already forward any W3C headers verbatim — nothing is stripped). The typed-array claim was stale as a defect (`src/` has zero `Buffer.from` call sites; the SDK's Web APIs are view-safe by construction, and adopting the proposed `Buffer`-coupled helpers would regress the edge-runtime zero-Node-builtins gate) — but the invariant is now pinned by tests. The companion upstream-specs digest (System One batching, payload caps, `expires_at`, model warmth) was comprehensively stale — every recommendation already ships. All additive, no breaking changes. 1012 pre-existing tests stay green, 12 new tests added.
6
+
7
+ ### Streaming blob uploads (MOD-01 — multi-GB GGUFs never enter the heap)
8
+
9
+ - **Transport**: both fetch call sites (middleware `finalHandler` and the direct path, previously duplicated init construction) now share one `buildFetchInit` that sets `duplex: 'half'` exactly when the body is a web `ReadableStream`. The guard is structural (`getReader` duck-typing) so cross-realm streams (workers, VM contexts) still get the member; the init type is a local `RequestInit & { duplex?: 'half' }` intersection because `@types/node` 22's `RequestInit` doesn't carry the member yet — assignable to the plain `RequestInit` `FetchLike` expects. Harmless (and spec'd) in browsers.
10
+ - **`createBlobFromStream(digest, stream, request?)`** — the multi-gigabyte-safe counterpart of `createBlobFromData`: pipes any web `ReadableStream<Uint8Array>` straight into the request body with HEAD-dedupe semantics identical to the data-based variant. The digest must be known up front — it _is_ the upload URL, and the server verifies streamed content against it, so a truncated or corrupted stream fails the upload instead of poisoning the store.
11
+ - **`createBlobFromFile` rewritten as two streaming passes** (same signature, same result shape): a SHA-256 hash pass through `node:crypto`'s incremental digest in 256 KiB chunks (skipped entirely when `opts.digest` is provided), then an upload pass piping `createReadStream` → `Readable.toWeb` into the streaming upload. Peak heap is O(chunk) in both passes; `createModelFromGguf` (which routes every shard through this method) inherits the bound. The method was already Node-only (hidden-specifier dynamic `node:fs`); the new `node:crypto`/`node:stream` imports follow the same pattern, keeping the main entry edge-portable.
12
+ - **Cancellation plumbing**: `createBlob`/`checkBlob` now accept `RequestCancellationOptions` (`signal`/`timeoutMs`) like their siblings, and `createBlobFromData`'s previously decorative `signal` option is actually wired through. Aborts surface as `OllamaAbortError` from both file passes (the hash pass checks between chunks and destroys the fd; the upload pass forwards the signal to `fetch` and destroys the stream on abort).
13
+ - New coverage pins the whole story: duplex injection (unit, injected `fetch`), byte-exact streamed uploads against a real capture server, the two-pass file flow (streaming digest matches the whole-file digest; wire bytes equal the file byte-for-byte), abort-before-traffic and abort-mid-stalled-upload, and daemonless public-mock round trips including `createModelFromGguf`'s headline flow (blob POST carries the shard, `/api/create` references it under its basename).
14
+
15
+ ### Typed-array view safety (BUF-01 — stale defect, pinned invariant)
16
+
17
+ - The audit found zero `Buffer.from(view.buffer)`-style call sites: `computeBlobDigest` uses Web Crypto, which hashes a `BufferSource` view's bytes per spec, and `fetch` serializes `Uint8Array` bodies per-view. The digest's `safeUint8ArrayToBuffer`/`toUint8Array` helpers were declined — they couple a runtime-portable SDK to Node's `Buffer` and would regress the edge-runtime gate (ADR 0032).
18
+ - The invariant is now pinned by the digest's own suggested probe, hardened with an external oracle: a `subarray(10, 20)` view of a 1024-byte patterned pool hashes to the standalone slice's digest (cross-checked against `node:crypto`) and arrives on the wire as exactly its 10 bytes — never the parent buffer.
19
+ - Cleanup: `computeBlobDigest`'s misleading `data as unknown as ArrayBuffer` cast removed (the local structural `SubtleCrypto` type already accepts `ArrayBufferView`).
20
+
21
+ ### Tracing headers (TRC-01 — stale, documented)
22
+
23
+ - The SDK forwards headers verbatim through three existing seams: client-level `headers` configuration (every request, every endpoint), per-request `headers` on transport request options (merged last, so they win), and middleware's mutable `RequestContext.headers` record — an OpenTelemetry-style extractor plugs in without touching a single method signature. The README's middleware section now shows the W3C `traceparent`/`tracestate`/`baggage` propagation pattern explicitly.
24
+ - The digest's `traceContext` option on every client method and ambient `AsyncLocalStorage` capture were declined (ADR 0032): signature inflation and hidden global state for capability that already exists.
25
+
26
+ ### Upstream-specs digest verdicts (all stale — pointers, no new API)
27
+
28
+ - **System One batching**: `client.systemOne()` already evaluates N named questions against shared state in one round trip, with compile-time question→answer type correlation the sketch lacks. **Payload caps**: `MAX_SYSTEM_ONE_REQUEST_BYTES` (64 KiB) / `MAX_SYSTEM_ONE_IMAGES_BYTES` (32 MiB) are exported, `estimateSystemOneRequestBytes()` is the pre-flight estimator, and the runtime enforces both client-side. **`expires_at`**: typed on `ModelResponse` from `/api/ps`, returned by `client.ps()`. **Model warmth**: `pin()`/`unload()`/`KEEP_ALIVE_INDEFINITE`/per-call `keep_alive`/`warmModelAffinity()` are the supported primitives; a background `ModelWarmthKeeper` timer daemon was declined (hidden event-loop work that escapes the client-teardown contract — reasoning in ADR 0032).
29
+
3
30
  ## [1.16.0] — Stream observation (`observeStream`), network-error classification & context-window recommendations
4
31
 
5
32
  The October 13 second digest ("Stream Multi-Sink Teeing, Context Window Overflow, Socket Hangup Recovery") targeted edge transport hazards and memory boundaries during high-throughput agent execution. Audited against the codebase first (see [ADR 0031](./docs/adr/0031-stream-observation-network-classification-context-recommendations.md)): the stream-teeing claim was the real gap (no observer utility existed, and `OllamaStream`'s dual modes are mutually exclusive — you couldn't tap a stream you were iterating); the context-overflow claim was comprehensively stale (a superior CJK-aware estimator with three-tier window resolution, `onContextOverflow` policies, and `getContextLength()` discovery has shipped since the v1.9.0 era — only its concrete-window suggestion was missing); and the socket-retry claim was inverted on the headline (every SDK-routed call already retries with backoff _and_ fails over — three retries where the digest asked for one) but real in one seam: network-error classification was `TypeError`-only, so code-carrying socket errors from custom `fetch` implementations and nested undici `cause` chains died un-retried on first attempt. All additive, no breaking changes. 977 pre-existing tests stay green, 35 new tests added.
package/README.md CHANGED
@@ -33,7 +33,7 @@ Nexum’s model harness (2026-10-09) exercised this SDK via `gemma4:cloud` and `
33
33
  - 🌉 **OpenAI & Anthropic Compatibility Bridges**: Built-in clients for `/v1/chat/completions`, `/v1/responses`, `/v1/models`, and `/v1/messages`, including `reasoning_effort`/`reasoning.effort` for thinking models — plus an ergonomic dual-mode `client.responses.create()` bridge that prefers native `/v1/responses` and transparently re-issues via `/api/chat` on older servers (pre-v0.13.3).
34
34
  - 💬 **KV-Cache-Aware Conversation Sessions**: `client.session(model, systemPrompt)` keeps an append-only, prefix-stable history that maximizes Ollama's KV-cache reuse across turns, surfaces per-turn + cumulative cache hit rates (`prompt_eval_cached_count`) so cache degradation is visible instead of silent, and offers a one-call `compact()` sliding-window escape hatch when a session outgrows its window.
35
35
  - 🛑 **Context-Window Safety**: Heuristic client-side token estimation warns (or throws, `onContextOverflow: 'throw'`) before a request is sent when the prompt approaches the effective `num_ctx` window — Ollama's default behavior is to _silently truncate_ oversized prompts — and `defaultContextLength` makes the window explicit on every request.
36
- - 📦 **Blob & Custom Model Publishing**: Content-addressed blob management per Ollama's documented protocol — `computeBlobDigest()` (SHA-256 → `sha256:<hex>`), `createBlobFromData()`/`createBlobFromFile()` (HEAD-check + upload, skipping existing blobs), and one-shot `createModelFromGguf()` (upload GGUF shard blobs, then `/api/create` with `files: {name: digest}`) — with **speculative-decoding assembly**: `draftGguf` uploads the draft network's shards into `draft_files`, `draft_quantize` compresses them independently, and `parameters.draft_num_predict` tunes the per-step draft length.
36
+ - 📦 **Blob & Custom Model Publishing**: Content-addressed blob management per Ollama's documented protocol — `computeBlobDigest()` (SHA-256 → `sha256:<hex>`), `createBlobFromData()`/`createBlobFromFile()` (HEAD-check + upload, skipping existing blobs), **streaming uploads with O(chunk) heap** (`createBlobFromFile` hashes and uploads in 256 KiB chunks — multi-GB GGUFs never enter the Node heap; `createBlobFromStream()` pipes any web `ReadableStream` straight into `fetch` with `duplex: 'half'`), and one-shot `createModelFromGguf()` (upload GGUF shard blobs, then `/api/create` with `files: {name: digest}`) — with **speculative-decoding assembly**: `draftGguf` uploads the draft network's shards into `draft_files`, `draft_quantize` compresses them independently, and `parameters.draft_num_predict` tunes the per-step draft length.
37
37
  - 🏁 **Speculative Draft Benchmark (`compareDraftPerformance`)**: One call answers "does drafting pay off for _this_ prompt?" — identical prompt on both sides, deterministic sampling (`temperature: 0` + fixed seed), warm-up discard, several samples per side with **medians** compared, throughput via `formatTelemetry` — reported as baseline vs. speculative tok/s, `speedupMultiplier`, and `effectiveGain` past a configurable threshold. A ≈1.0 speedup is itself diagnostic: the model has no draft network, so the server silently ignores the option.
38
38
  - 🧪 **In-Memory Mock Server (`@nemesis-oss/ollama-sdk/testing`)**: The SDK's own deterministic test double, promoted public — `createMockOllamaServer()` with scriptable NDJSON routes, built-in liveness/version defaults (so `ping()` works out of the box), request recording for wire-level assertions, **request-aware routes** (`responder` — per-request responses for fail-twice-then-succeed retry drills and body-dependent payloads), mid-stream **in-band error injection** (`{"error": "..."}` frames at HTTP 200, exactly how a real daemon reports GPU OOM), connection drops, and correctly-shaped chunk builders for streaming text and **parallel tool calls** — zero network, zero daemon, CI-deterministic.
39
39
  - 🧵 **Batched Embeddings with Backpressure**: `client.embedBatch()` splits corpora of any size into bounded `/api/embed` batches (default 32 inputs × 3 in flight) — order-preserving, fail-fast, progress-reporting — so RAG/vector ingestion can't OOM a local daemon or saturate `OLLAMA_MAX_QUEUE` the way `Promise.all` floods do, with per-string context-window pre-flight against silent truncation.
@@ -500,6 +500,34 @@ await client.models.createModelFromGguf('my-model', [
500
500
  ]);
501
501
  ```
502
502
 
503
+ #### Streaming uploads — multi-gigabyte weights with O(chunk) heap
504
+
505
+ Both `createBlobFromFile` and `createModelFromGguf` (which routes every shard
506
+ through it) stream instead of bulk-reading: the SHA-256 hash pass and the
507
+ upload pass each consume the file in 256 KiB chunks, so peak heap is a chunk,
508
+ never the file. Pass `digest` when you already know it to skip the hash pass
509
+ entirely. When you own the bytes (a chunk producer, a WASM/SharedArrayBuffer
510
+ pipeline, a resumed download), `createBlobFromStream` pipes any web
511
+ `ReadableStream<Uint8Array>` straight into the request body — the transport
512
+ sets `duplex: 'half'` (required by Node 18+ `fetch` for stream bodies)
513
+ automatically, and the server verifies the streamed content against the
514
+ digest, so a truncated stream fails the upload instead of corrupting the
515
+ store:
516
+
517
+ ```typescript
518
+ // digest known up front (hash first when it isn't — the digest IS the URL)
519
+ const { alreadyExisted } = await client.models.createBlobFromStream(
520
+ 'sha256:…',
521
+ myChunkProducer, // ReadableStream<Uint8Array>
522
+ );
523
+
524
+ // Both methods accept { signal, timeoutMs } for cancellation/timeout:
525
+ await client.models.createBlobFromFile('/models/model.gguf', {
526
+ digest: 'sha256:…', // optional: skip the hash pass
527
+ signal: controller.signal,
528
+ });
529
+ ```
530
+
503
531
  ### Speculative Decoding (Draft Models)
504
532
 
505
533
  Ollama packages speculative decoding as a small **draft network** whose token
@@ -1934,6 +1962,25 @@ around the underlying HTTP request for native endpoints, OpenAI/Anthropic compat
1934
1962
  model health checks, and the hosted web tools. Lifecycle events expose `start`, `success`,
1935
1963
  `retry`, and `error` events with a request id, timing, and status/error information.
1936
1964
 
1965
+ **Distributed tracing (W3C `traceparent`/`tracestate`/`baggage`)**: the SDK forwards
1966
+ whatever headers it is given and never strips or rewrites yours — three seams cover
1967
+ propagation. Client-wide (every request, every endpoint):
1968
+
1969
+ ```ts
1970
+ const client = new OllamaClient({
1971
+ headers: {
1972
+ traceparent: carrierHeader('traceparent'), // from your inbound request
1973
+ baggage: carrierHeader('baggage'),
1974
+ },
1975
+ });
1976
+ ```
1977
+
1978
+ Per-request or computed (the middleware seam from above — `request.headers` is a mutable
1979
+ record, so an OpenTelemetry-style extractor plugs in directly and can vary headers per
1980
+ call). A `traceContext` option on every SDK method was considered and declined
1981
+ (ADR 0032): it would inflate every signature for something these seams already do
1982
+ without touching them.
1983
+
1937
1984
  ```ts
1938
1985
  const client = new OllamaClient({
1939
1986
  middleware: [
package/dist/index.cjs CHANGED
@@ -1113,6 +1113,18 @@ function createRequestId() {
1113
1113
  requestSequence += 1;
1114
1114
  return `ollama-http-${requestSequence}`;
1115
1115
  }
1116
+ function isWebReadableStreamBody(body) {
1117
+ return typeof body === "object" && body !== null && typeof body.getReader === "function";
1118
+ }
1119
+ function buildFetchInit(req) {
1120
+ return {
1121
+ method: req.method,
1122
+ headers: req.headers,
1123
+ ...req.body !== void 0 ? { body: req.body } : {},
1124
+ ...req.signal !== void 0 ? { signal: req.signal } : {},
1125
+ ...isWebReadableStreamBody(req.body) ? { duplex: "half" } : {}
1126
+ };
1127
+ }
1116
1128
  function sameHeaders(actual, expected) {
1117
1129
  const actualEntries = Object.fromEntries(
1118
1130
  Object.entries(actual).map(([key, value]) => [key.toLowerCase(), value])
@@ -1162,13 +1174,7 @@ var HttpClient = class {
1162
1174
  timestamp: startedAt
1163
1175
  });
1164
1176
  const finalHandler = async (req) => {
1165
- const init = {
1166
- method: req.method,
1167
- headers: req.headers,
1168
- ...req.body !== void 0 ? { body: req.body } : {},
1169
- ...req.signal !== void 0 ? { signal: req.signal } : {}
1170
- };
1171
- const response = await this.fetchImpl(req.url, init);
1177
+ const response = await this.fetchImpl(req.url, buildFetchInit(req));
1172
1178
  rawResponse = response;
1173
1179
  const responseHeaders = response.headers && typeof response.headers.entries === "function" ? Object.fromEntries(response.headers.entries()) : {};
1174
1180
  return {
@@ -1179,12 +1185,7 @@ var HttpClient = class {
1179
1185
  };
1180
1186
  try {
1181
1187
  if (this.middleware.length === 0) {
1182
- const response2 = await this.fetchImpl(request.url, {
1183
- method: request.method,
1184
- headers: request.headers,
1185
- ...request.body !== void 0 ? { body: request.body } : {},
1186
- ...request.signal !== void 0 ? { signal: request.signal } : {}
1187
- });
1188
+ const response2 = await this.fetchImpl(request.url, buildFetchInit(request));
1188
1189
  if (response2.status >= 200 && response2.status < 300) {
1189
1190
  this.onLifecycleEvent?.({
1190
1191
  type: "success",
@@ -2775,7 +2776,7 @@ var ModelsClient = class {
2775
2776
  { model, singleEndpoint: true }
2776
2777
  );
2777
2778
  }
2778
- async createBlob(digest, data) {
2779
+ async createBlob(digest, data, request) {
2779
2780
  await this.runner(
2780
2781
  (http, signal) => http.request({
2781
2782
  path: `/api/blobs/${encodeURIComponent(digest)}`,
@@ -2783,10 +2784,10 @@ var ModelsClient = class {
2783
2784
  rawBody: data,
2784
2785
  signal
2785
2786
  }),
2786
- { singleEndpoint: true }
2787
+ { ...request, singleEndpoint: true }
2787
2788
  );
2788
2789
  }
2789
- async checkBlob(digest) {
2790
+ async checkBlob(digest, request) {
2790
2791
  try {
2791
2792
  await this.runner(
2792
2793
  (http, signal) => http.request({
@@ -2794,7 +2795,7 @@ var ModelsClient = class {
2794
2795
  method: "HEAD",
2795
2796
  signal
2796
2797
  }),
2797
- { singleEndpoint: true }
2798
+ { ...request, singleEndpoint: true }
2798
2799
  );
2799
2800
  return true;
2800
2801
  } catch (err) {
@@ -2836,9 +2837,32 @@ var ModelsClient = class {
2836
2837
  */
2837
2838
  async createBlobFromData(data, opts) {
2838
2839
  const digest = opts?.digest ?? await this.computeBlobDigest(data);
2839
- const alreadyExisted = await this.checkBlob(digest);
2840
+ const alreadyExisted = await this.checkBlob(digest, opts);
2840
2841
  if (!alreadyExisted) {
2841
- await this.createBlob(digest, data);
2842
+ await this.createBlob(digest, data, opts);
2843
+ }
2844
+ return { digest, alreadyExisted };
2845
+ }
2846
+ /**
2847
+ * Streams a blob upload straight from a web `ReadableStream` — the
2848
+ * multi-gigabyte-safe counterpart of {@link createBlobFromData}. The body
2849
+ * is never materialized in memory: the transport hands the stream to
2850
+ * `fetch` directly (setting `duplex: 'half'`, which Node 18+ requires for
2851
+ * stream bodies), so peak heap usage is a transport-level chunk, not the
2852
+ * blob size.
2853
+ *
2854
+ * The digest must be known up front — it *is* the upload URL
2855
+ * (`/api/blobs/<digest>`), the same content-addressed protocol
2856
+ * `curl -T` performs. Hash first when you don't have it: in-memory bytes
2857
+ * via {@link computeBlobDigest}, files via {@link createBlobFromFile}
2858
+ * (which hashes in a streaming pass). The server verifies the streamed
2859
+ * bytes against the digest and rejects mismatches, so a corrupted or
2860
+ * truncated stream fails the upload instead of poisoning the store.
2861
+ */
2862
+ async createBlobFromStream(digest, stream, request) {
2863
+ const alreadyExisted = await this.checkBlob(digest, request);
2864
+ if (!alreadyExisted) {
2865
+ await this.createBlob(digest, stream, request);
2842
2866
  }
2843
2867
  return { digest, alreadyExisted };
2844
2868
  }
@@ -2847,16 +2871,61 @@ var ModelsClient = class {
2847
2871
  * `node:fs` import, so browser bundles never pull it in). The file name
2848
2872
  * (basename) is what `/api/create`'s `files` map expects as the key, so
2849
2873
  * this returns it alongside the digest.
2874
+ *
2875
+ * Streams both passes in fixed-size chunks — peak heap is O(chunk), not
2876
+ * O(file), so multi-gigabyte GGUFs never enter the Node heap:
2877
+ *
2878
+ * 1. **Hash pass** (skipped when `opts.digest` is provided): the file is
2879
+ * streamed through `node:crypto`'s incremental SHA-256.
2880
+ * 2. **Upload pass**: `node:fs` createReadStream → web ReadableStream →
2881
+ * `fetch` body with `duplex: 'half'` (see {@link createBlobFromStream}).
2882
+ *
2883
+ * A pre-existing blob (HEAD 200) skips the upload pass entirely, same as
2884
+ * {@link createBlobFromData}.
2850
2885
  */
2851
2886
  async createBlobFromFile(path, opts) {
2852
2887
  const nodeFsModule = "node:fs";
2853
- const { promises: fs } = await import(nodeFsModule);
2854
- const data = await fs.readFile(path);
2855
- const result = await this.createBlobFromData(new Uint8Array(data), {
2856
- ...opts
2857
- });
2888
+ const { createReadStream } = await import(nodeFsModule);
2889
+ const nodeCryptoModule = "node:crypto";
2890
+ const { createHash } = await import(nodeCryptoModule);
2891
+ const nodeStreamModule = "node:stream";
2892
+ const { Readable } = await import(nodeStreamModule);
2893
+ const CHUNK_BYTES = 256 * 1024;
2894
+ let digest = opts?.digest;
2895
+ if (digest === void 0) {
2896
+ const hash = createHash("sha256");
2897
+ const hashStream = createReadStream(path, { highWaterMark: CHUNK_BYTES });
2898
+ try {
2899
+ for await (const chunk of hashStream) {
2900
+ if (opts?.signal?.aborted) {
2901
+ throw new OllamaAbortError("createBlobFromFile: aborted while hashing", {});
2902
+ }
2903
+ hash.update(chunk);
2904
+ }
2905
+ } finally {
2906
+ hashStream.destroy();
2907
+ }
2908
+ digest = `sha256:${hash.digest("hex")}`;
2909
+ }
2910
+ const alreadyExisted = await this.checkBlob(digest, opts);
2911
+ if (!alreadyExisted) {
2912
+ const uploadStream = createReadStream(path, { highWaterMark: CHUNK_BYTES });
2913
+ const onAbort = () => {
2914
+ uploadStream.destroy();
2915
+ };
2916
+ opts?.signal?.addEventListener("abort", onAbort, { once: true });
2917
+ try {
2918
+ await this.createBlob(
2919
+ digest,
2920
+ Readable.toWeb(uploadStream),
2921
+ opts
2922
+ );
2923
+ } finally {
2924
+ opts?.signal?.removeEventListener("abort", onAbort);
2925
+ }
2926
+ }
2858
2927
  const fileName = path.split(/[\\/]/).pop() ?? path;
2859
- return { ...result, fileName };
2928
+ return { digest, alreadyExisted, fileName };
2860
2929
  }
2861
2930
  /**
2862
2931
  * One-shot custom-model publishing from raw GGUF weights, following the
@@ -6196,8 +6265,8 @@ var OllamaClient = class _OllamaClient {
6196
6265
  */
6197
6266
  getContextLength = (request) => this.models.getContextLength(request);
6198
6267
  version = () => this.models.version();
6199
- createBlob = (digest, data) => this.models.createBlob(digest, data);
6200
- checkBlob = (digest) => this.models.checkBlob(digest);
6268
+ createBlob = (digest, data, request) => this.models.createBlob(digest, data, request);
6269
+ checkBlob = (digest, request) => this.models.checkBlob(digest, request);
6201
6270
  /**
6202
6271
  * Immediately unloads `model` from VRAM. Convenience alias for
6203
6272
  * {@link ModelsClient.unload} — see that method for the full