@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 +27 -0
- package/README.md +48 -1
- package/dist/index.cjs +96 -27
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +35 -8
- package/dist/index.d.ts +35 -8
- package/dist/index.js +96 -27
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.d.cts
CHANGED
|
@@ -970,8 +970,8 @@ declare class ModelsClient {
|
|
|
970
970
|
* model-not-found (404), or VRAM-exhaustion failures.
|
|
971
971
|
*/
|
|
972
972
|
pin(model: string): Promise<void>;
|
|
973
|
-
createBlob(digest: string, data: BinaryBody): Promise<void>;
|
|
974
|
-
checkBlob(digest: string): Promise<boolean>;
|
|
973
|
+
createBlob(digest: string, data: BinaryBody, request?: RequestCancellationOptions): Promise<void>;
|
|
974
|
+
checkBlob(digest: string, request?: RequestCancellationOptions): Promise<boolean>;
|
|
975
975
|
/**
|
|
976
976
|
* Computes a blob digest the way Ollama's content-addressed store expects:
|
|
977
977
|
* SHA-256 over the raw bytes, formatted as `sha256:<64 lowercase hex>`.
|
|
@@ -992,17 +992,44 @@ declare class ModelsClient {
|
|
|
992
992
|
*/
|
|
993
993
|
createBlobFromData(data: Uint8Array, opts?: {
|
|
994
994
|
readonly digest?: string | undefined;
|
|
995
|
-
|
|
996
|
-
|
|
995
|
+
} & RequestCancellationOptions): Promise<BlobUploadResult>;
|
|
996
|
+
/**
|
|
997
|
+
* Streams a blob upload straight from a web `ReadableStream` — the
|
|
998
|
+
* multi-gigabyte-safe counterpart of {@link createBlobFromData}. The body
|
|
999
|
+
* is never materialized in memory: the transport hands the stream to
|
|
1000
|
+
* `fetch` directly (setting `duplex: 'half'`, which Node 18+ requires for
|
|
1001
|
+
* stream bodies), so peak heap usage is a transport-level chunk, not the
|
|
1002
|
+
* blob size.
|
|
1003
|
+
*
|
|
1004
|
+
* The digest must be known up front — it *is* the upload URL
|
|
1005
|
+
* (`/api/blobs/<digest>`), the same content-addressed protocol
|
|
1006
|
+
* `curl -T` performs. Hash first when you don't have it: in-memory bytes
|
|
1007
|
+
* via {@link computeBlobDigest}, files via {@link createBlobFromFile}
|
|
1008
|
+
* (which hashes in a streaming pass). The server verifies the streamed
|
|
1009
|
+
* bytes against the digest and rejects mismatches, so a corrupted or
|
|
1010
|
+
* truncated stream fails the upload instead of poisoning the store.
|
|
1011
|
+
*/
|
|
1012
|
+
createBlobFromStream(digest: string, stream: ReadableStream<Uint8Array>, request?: RequestCancellationOptions): Promise<BlobUploadResult>;
|
|
997
1013
|
/**
|
|
998
1014
|
* Reads a local file and uploads it as a blob (Node.js only — dynamic
|
|
999
1015
|
* `node:fs` import, so browser bundles never pull it in). The file name
|
|
1000
1016
|
* (basename) is what `/api/create`'s `files` map expects as the key, so
|
|
1001
1017
|
* this returns it alongside the digest.
|
|
1018
|
+
*
|
|
1019
|
+
* Streams both passes in fixed-size chunks — peak heap is O(chunk), not
|
|
1020
|
+
* O(file), so multi-gigabyte GGUFs never enter the Node heap:
|
|
1021
|
+
*
|
|
1022
|
+
* 1. **Hash pass** (skipped when `opts.digest` is provided): the file is
|
|
1023
|
+
* streamed through `node:crypto`'s incremental SHA-256.
|
|
1024
|
+
* 2. **Upload pass**: `node:fs` createReadStream → web ReadableStream →
|
|
1025
|
+
* `fetch` body with `duplex: 'half'` (see {@link createBlobFromStream}).
|
|
1026
|
+
*
|
|
1027
|
+
* A pre-existing blob (HEAD 200) skips the upload pass entirely, same as
|
|
1028
|
+
* {@link createBlobFromData}.
|
|
1002
1029
|
*/
|
|
1003
1030
|
createBlobFromFile(path: string, opts?: {
|
|
1004
|
-
readonly
|
|
1005
|
-
}): Promise<BlobUploadResult & {
|
|
1031
|
+
readonly digest?: string | undefined;
|
|
1032
|
+
} & RequestCancellationOptions): Promise<BlobUploadResult & {
|
|
1006
1033
|
readonly fileName: string;
|
|
1007
1034
|
}>;
|
|
1008
1035
|
/**
|
|
@@ -2277,8 +2304,8 @@ declare class OllamaClient {
|
|
|
2277
2304
|
*/
|
|
2278
2305
|
readonly getContextLength: (request: ContextDiscoveryRequestOptions) => Promise<DiscoveredContextLength>;
|
|
2279
2306
|
readonly version: () => Promise<VersionResponse>;
|
|
2280
|
-
readonly createBlob: (digest: string, data: BinaryBody) => Promise<void>;
|
|
2281
|
-
readonly checkBlob: (digest: string) => Promise<boolean>;
|
|
2307
|
+
readonly createBlob: (digest: string, data: BinaryBody, request?: RequestCancellationOptions) => Promise<void>;
|
|
2308
|
+
readonly checkBlob: (digest: string, request?: RequestCancellationOptions) => Promise<boolean>;
|
|
2282
2309
|
/**
|
|
2283
2310
|
* Immediately unloads `model` from VRAM. Convenience alias for
|
|
2284
2311
|
* {@link ModelsClient.unload} — see that method for the full
|
package/dist/index.d.ts
CHANGED
|
@@ -970,8 +970,8 @@ declare class ModelsClient {
|
|
|
970
970
|
* model-not-found (404), or VRAM-exhaustion failures.
|
|
971
971
|
*/
|
|
972
972
|
pin(model: string): Promise<void>;
|
|
973
|
-
createBlob(digest: string, data: BinaryBody): Promise<void>;
|
|
974
|
-
checkBlob(digest: string): Promise<boolean>;
|
|
973
|
+
createBlob(digest: string, data: BinaryBody, request?: RequestCancellationOptions): Promise<void>;
|
|
974
|
+
checkBlob(digest: string, request?: RequestCancellationOptions): Promise<boolean>;
|
|
975
975
|
/**
|
|
976
976
|
* Computes a blob digest the way Ollama's content-addressed store expects:
|
|
977
977
|
* SHA-256 over the raw bytes, formatted as `sha256:<64 lowercase hex>`.
|
|
@@ -992,17 +992,44 @@ declare class ModelsClient {
|
|
|
992
992
|
*/
|
|
993
993
|
createBlobFromData(data: Uint8Array, opts?: {
|
|
994
994
|
readonly digest?: string | undefined;
|
|
995
|
-
|
|
996
|
-
|
|
995
|
+
} & RequestCancellationOptions): Promise<BlobUploadResult>;
|
|
996
|
+
/**
|
|
997
|
+
* Streams a blob upload straight from a web `ReadableStream` — the
|
|
998
|
+
* multi-gigabyte-safe counterpart of {@link createBlobFromData}. The body
|
|
999
|
+
* is never materialized in memory: the transport hands the stream to
|
|
1000
|
+
* `fetch` directly (setting `duplex: 'half'`, which Node 18+ requires for
|
|
1001
|
+
* stream bodies), so peak heap usage is a transport-level chunk, not the
|
|
1002
|
+
* blob size.
|
|
1003
|
+
*
|
|
1004
|
+
* The digest must be known up front — it *is* the upload URL
|
|
1005
|
+
* (`/api/blobs/<digest>`), the same content-addressed protocol
|
|
1006
|
+
* `curl -T` performs. Hash first when you don't have it: in-memory bytes
|
|
1007
|
+
* via {@link computeBlobDigest}, files via {@link createBlobFromFile}
|
|
1008
|
+
* (which hashes in a streaming pass). The server verifies the streamed
|
|
1009
|
+
* bytes against the digest and rejects mismatches, so a corrupted or
|
|
1010
|
+
* truncated stream fails the upload instead of poisoning the store.
|
|
1011
|
+
*/
|
|
1012
|
+
createBlobFromStream(digest: string, stream: ReadableStream<Uint8Array>, request?: RequestCancellationOptions): Promise<BlobUploadResult>;
|
|
997
1013
|
/**
|
|
998
1014
|
* Reads a local file and uploads it as a blob (Node.js only — dynamic
|
|
999
1015
|
* `node:fs` import, so browser bundles never pull it in). The file name
|
|
1000
1016
|
* (basename) is what `/api/create`'s `files` map expects as the key, so
|
|
1001
1017
|
* this returns it alongside the digest.
|
|
1018
|
+
*
|
|
1019
|
+
* Streams both passes in fixed-size chunks — peak heap is O(chunk), not
|
|
1020
|
+
* O(file), so multi-gigabyte GGUFs never enter the Node heap:
|
|
1021
|
+
*
|
|
1022
|
+
* 1. **Hash pass** (skipped when `opts.digest` is provided): the file is
|
|
1023
|
+
* streamed through `node:crypto`'s incremental SHA-256.
|
|
1024
|
+
* 2. **Upload pass**: `node:fs` createReadStream → web ReadableStream →
|
|
1025
|
+
* `fetch` body with `duplex: 'half'` (see {@link createBlobFromStream}).
|
|
1026
|
+
*
|
|
1027
|
+
* A pre-existing blob (HEAD 200) skips the upload pass entirely, same as
|
|
1028
|
+
* {@link createBlobFromData}.
|
|
1002
1029
|
*/
|
|
1003
1030
|
createBlobFromFile(path: string, opts?: {
|
|
1004
|
-
readonly
|
|
1005
|
-
}): Promise<BlobUploadResult & {
|
|
1031
|
+
readonly digest?: string | undefined;
|
|
1032
|
+
} & RequestCancellationOptions): Promise<BlobUploadResult & {
|
|
1006
1033
|
readonly fileName: string;
|
|
1007
1034
|
}>;
|
|
1008
1035
|
/**
|
|
@@ -2277,8 +2304,8 @@ declare class OllamaClient {
|
|
|
2277
2304
|
*/
|
|
2278
2305
|
readonly getContextLength: (request: ContextDiscoveryRequestOptions) => Promise<DiscoveredContextLength>;
|
|
2279
2306
|
readonly version: () => Promise<VersionResponse>;
|
|
2280
|
-
readonly createBlob: (digest: string, data: BinaryBody) => Promise<void>;
|
|
2281
|
-
readonly checkBlob: (digest: string) => Promise<boolean>;
|
|
2307
|
+
readonly createBlob: (digest: string, data: BinaryBody, request?: RequestCancellationOptions) => Promise<void>;
|
|
2308
|
+
readonly checkBlob: (digest: string, request?: RequestCancellationOptions) => Promise<boolean>;
|
|
2282
2309
|
/**
|
|
2283
2310
|
* Immediately unloads `model` from VRAM. Convenience alias for
|
|
2284
2311
|
* {@link ModelsClient.unload} — see that method for the full
|
package/dist/index.js
CHANGED
|
@@ -1111,6 +1111,18 @@ function createRequestId() {
|
|
|
1111
1111
|
requestSequence += 1;
|
|
1112
1112
|
return `ollama-http-${requestSequence}`;
|
|
1113
1113
|
}
|
|
1114
|
+
function isWebReadableStreamBody(body) {
|
|
1115
|
+
return typeof body === "object" && body !== null && typeof body.getReader === "function";
|
|
1116
|
+
}
|
|
1117
|
+
function buildFetchInit(req) {
|
|
1118
|
+
return {
|
|
1119
|
+
method: req.method,
|
|
1120
|
+
headers: req.headers,
|
|
1121
|
+
...req.body !== void 0 ? { body: req.body } : {},
|
|
1122
|
+
...req.signal !== void 0 ? { signal: req.signal } : {},
|
|
1123
|
+
...isWebReadableStreamBody(req.body) ? { duplex: "half" } : {}
|
|
1124
|
+
};
|
|
1125
|
+
}
|
|
1114
1126
|
function sameHeaders(actual, expected) {
|
|
1115
1127
|
const actualEntries = Object.fromEntries(
|
|
1116
1128
|
Object.entries(actual).map(([key, value]) => [key.toLowerCase(), value])
|
|
@@ -1160,13 +1172,7 @@ var HttpClient = class {
|
|
|
1160
1172
|
timestamp: startedAt
|
|
1161
1173
|
});
|
|
1162
1174
|
const finalHandler = async (req) => {
|
|
1163
|
-
const
|
|
1164
|
-
method: req.method,
|
|
1165
|
-
headers: req.headers,
|
|
1166
|
-
...req.body !== void 0 ? { body: req.body } : {},
|
|
1167
|
-
...req.signal !== void 0 ? { signal: req.signal } : {}
|
|
1168
|
-
};
|
|
1169
|
-
const response = await this.fetchImpl(req.url, init);
|
|
1175
|
+
const response = await this.fetchImpl(req.url, buildFetchInit(req));
|
|
1170
1176
|
rawResponse = response;
|
|
1171
1177
|
const responseHeaders = response.headers && typeof response.headers.entries === "function" ? Object.fromEntries(response.headers.entries()) : {};
|
|
1172
1178
|
return {
|
|
@@ -1177,12 +1183,7 @@ var HttpClient = class {
|
|
|
1177
1183
|
};
|
|
1178
1184
|
try {
|
|
1179
1185
|
if (this.middleware.length === 0) {
|
|
1180
|
-
const response2 = await this.fetchImpl(request.url,
|
|
1181
|
-
method: request.method,
|
|
1182
|
-
headers: request.headers,
|
|
1183
|
-
...request.body !== void 0 ? { body: request.body } : {},
|
|
1184
|
-
...request.signal !== void 0 ? { signal: request.signal } : {}
|
|
1185
|
-
});
|
|
1186
|
+
const response2 = await this.fetchImpl(request.url, buildFetchInit(request));
|
|
1186
1187
|
if (response2.status >= 200 && response2.status < 300) {
|
|
1187
1188
|
this.onLifecycleEvent?.({
|
|
1188
1189
|
type: "success",
|
|
@@ -2773,7 +2774,7 @@ var ModelsClient = class {
|
|
|
2773
2774
|
{ model, singleEndpoint: true }
|
|
2774
2775
|
);
|
|
2775
2776
|
}
|
|
2776
|
-
async createBlob(digest, data) {
|
|
2777
|
+
async createBlob(digest, data, request) {
|
|
2777
2778
|
await this.runner(
|
|
2778
2779
|
(http, signal) => http.request({
|
|
2779
2780
|
path: `/api/blobs/${encodeURIComponent(digest)}`,
|
|
@@ -2781,10 +2782,10 @@ var ModelsClient = class {
|
|
|
2781
2782
|
rawBody: data,
|
|
2782
2783
|
signal
|
|
2783
2784
|
}),
|
|
2784
|
-
{ singleEndpoint: true }
|
|
2785
|
+
{ ...request, singleEndpoint: true }
|
|
2785
2786
|
);
|
|
2786
2787
|
}
|
|
2787
|
-
async checkBlob(digest) {
|
|
2788
|
+
async checkBlob(digest, request) {
|
|
2788
2789
|
try {
|
|
2789
2790
|
await this.runner(
|
|
2790
2791
|
(http, signal) => http.request({
|
|
@@ -2792,7 +2793,7 @@ var ModelsClient = class {
|
|
|
2792
2793
|
method: "HEAD",
|
|
2793
2794
|
signal
|
|
2794
2795
|
}),
|
|
2795
|
-
{ singleEndpoint: true }
|
|
2796
|
+
{ ...request, singleEndpoint: true }
|
|
2796
2797
|
);
|
|
2797
2798
|
return true;
|
|
2798
2799
|
} catch (err) {
|
|
@@ -2834,9 +2835,32 @@ var ModelsClient = class {
|
|
|
2834
2835
|
*/
|
|
2835
2836
|
async createBlobFromData(data, opts) {
|
|
2836
2837
|
const digest = opts?.digest ?? await this.computeBlobDigest(data);
|
|
2837
|
-
const alreadyExisted = await this.checkBlob(digest);
|
|
2838
|
+
const alreadyExisted = await this.checkBlob(digest, opts);
|
|
2838
2839
|
if (!alreadyExisted) {
|
|
2839
|
-
await this.createBlob(digest, data);
|
|
2840
|
+
await this.createBlob(digest, data, opts);
|
|
2841
|
+
}
|
|
2842
|
+
return { digest, alreadyExisted };
|
|
2843
|
+
}
|
|
2844
|
+
/**
|
|
2845
|
+
* Streams a blob upload straight from a web `ReadableStream` — the
|
|
2846
|
+
* multi-gigabyte-safe counterpart of {@link createBlobFromData}. The body
|
|
2847
|
+
* is never materialized in memory: the transport hands the stream to
|
|
2848
|
+
* `fetch` directly (setting `duplex: 'half'`, which Node 18+ requires for
|
|
2849
|
+
* stream bodies), so peak heap usage is a transport-level chunk, not the
|
|
2850
|
+
* blob size.
|
|
2851
|
+
*
|
|
2852
|
+
* The digest must be known up front — it *is* the upload URL
|
|
2853
|
+
* (`/api/blobs/<digest>`), the same content-addressed protocol
|
|
2854
|
+
* `curl -T` performs. Hash first when you don't have it: in-memory bytes
|
|
2855
|
+
* via {@link computeBlobDigest}, files via {@link createBlobFromFile}
|
|
2856
|
+
* (which hashes in a streaming pass). The server verifies the streamed
|
|
2857
|
+
* bytes against the digest and rejects mismatches, so a corrupted or
|
|
2858
|
+
* truncated stream fails the upload instead of poisoning the store.
|
|
2859
|
+
*/
|
|
2860
|
+
async createBlobFromStream(digest, stream, request) {
|
|
2861
|
+
const alreadyExisted = await this.checkBlob(digest, request);
|
|
2862
|
+
if (!alreadyExisted) {
|
|
2863
|
+
await this.createBlob(digest, stream, request);
|
|
2840
2864
|
}
|
|
2841
2865
|
return { digest, alreadyExisted };
|
|
2842
2866
|
}
|
|
@@ -2845,16 +2869,61 @@ var ModelsClient = class {
|
|
|
2845
2869
|
* `node:fs` import, so browser bundles never pull it in). The file name
|
|
2846
2870
|
* (basename) is what `/api/create`'s `files` map expects as the key, so
|
|
2847
2871
|
* this returns it alongside the digest.
|
|
2872
|
+
*
|
|
2873
|
+
* Streams both passes in fixed-size chunks — peak heap is O(chunk), not
|
|
2874
|
+
* O(file), so multi-gigabyte GGUFs never enter the Node heap:
|
|
2875
|
+
*
|
|
2876
|
+
* 1. **Hash pass** (skipped when `opts.digest` is provided): the file is
|
|
2877
|
+
* streamed through `node:crypto`'s incremental SHA-256.
|
|
2878
|
+
* 2. **Upload pass**: `node:fs` createReadStream → web ReadableStream →
|
|
2879
|
+
* `fetch` body with `duplex: 'half'` (see {@link createBlobFromStream}).
|
|
2880
|
+
*
|
|
2881
|
+
* A pre-existing blob (HEAD 200) skips the upload pass entirely, same as
|
|
2882
|
+
* {@link createBlobFromData}.
|
|
2848
2883
|
*/
|
|
2849
2884
|
async createBlobFromFile(path, opts) {
|
|
2850
2885
|
const nodeFsModule = "node:fs";
|
|
2851
|
-
const {
|
|
2852
|
-
const
|
|
2853
|
-
const
|
|
2854
|
-
|
|
2855
|
-
});
|
|
2886
|
+
const { createReadStream } = await import(nodeFsModule);
|
|
2887
|
+
const nodeCryptoModule = "node:crypto";
|
|
2888
|
+
const { createHash } = await import(nodeCryptoModule);
|
|
2889
|
+
const nodeStreamModule = "node:stream";
|
|
2890
|
+
const { Readable } = await import(nodeStreamModule);
|
|
2891
|
+
const CHUNK_BYTES = 256 * 1024;
|
|
2892
|
+
let digest = opts?.digest;
|
|
2893
|
+
if (digest === void 0) {
|
|
2894
|
+
const hash = createHash("sha256");
|
|
2895
|
+
const hashStream = createReadStream(path, { highWaterMark: CHUNK_BYTES });
|
|
2896
|
+
try {
|
|
2897
|
+
for await (const chunk of hashStream) {
|
|
2898
|
+
if (opts?.signal?.aborted) {
|
|
2899
|
+
throw new OllamaAbortError("createBlobFromFile: aborted while hashing", {});
|
|
2900
|
+
}
|
|
2901
|
+
hash.update(chunk);
|
|
2902
|
+
}
|
|
2903
|
+
} finally {
|
|
2904
|
+
hashStream.destroy();
|
|
2905
|
+
}
|
|
2906
|
+
digest = `sha256:${hash.digest("hex")}`;
|
|
2907
|
+
}
|
|
2908
|
+
const alreadyExisted = await this.checkBlob(digest, opts);
|
|
2909
|
+
if (!alreadyExisted) {
|
|
2910
|
+
const uploadStream = createReadStream(path, { highWaterMark: CHUNK_BYTES });
|
|
2911
|
+
const onAbort = () => {
|
|
2912
|
+
uploadStream.destroy();
|
|
2913
|
+
};
|
|
2914
|
+
opts?.signal?.addEventListener("abort", onAbort, { once: true });
|
|
2915
|
+
try {
|
|
2916
|
+
await this.createBlob(
|
|
2917
|
+
digest,
|
|
2918
|
+
Readable.toWeb(uploadStream),
|
|
2919
|
+
opts
|
|
2920
|
+
);
|
|
2921
|
+
} finally {
|
|
2922
|
+
opts?.signal?.removeEventListener("abort", onAbort);
|
|
2923
|
+
}
|
|
2924
|
+
}
|
|
2856
2925
|
const fileName = path.split(/[\\/]/).pop() ?? path;
|
|
2857
|
-
return {
|
|
2926
|
+
return { digest, alreadyExisted, fileName };
|
|
2858
2927
|
}
|
|
2859
2928
|
/**
|
|
2860
2929
|
* One-shot custom-model publishing from raw GGUF weights, following the
|
|
@@ -6194,8 +6263,8 @@ var OllamaClient = class _OllamaClient {
|
|
|
6194
6263
|
*/
|
|
6195
6264
|
getContextLength = (request) => this.models.getContextLength(request);
|
|
6196
6265
|
version = () => this.models.version();
|
|
6197
|
-
createBlob = (digest, data) => this.models.createBlob(digest, data);
|
|
6198
|
-
checkBlob = (digest) => this.models.checkBlob(digest);
|
|
6266
|
+
createBlob = (digest, data, request) => this.models.createBlob(digest, data, request);
|
|
6267
|
+
checkBlob = (digest, request) => this.models.checkBlob(digest, request);
|
|
6199
6268
|
/**
|
|
6200
6269
|
* Immediately unloads `model` from VRAM. Convenience alias for
|
|
6201
6270
|
* {@link ModelsClient.unload} — see that method for the full
|