@nemesis-oss/ollama-sdk 1.0.0 → 1.1.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
@@ -5,6 +5,40 @@ All notable changes to `@nemesis-oss/ollama-sdk` will be documented in this file
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [1.1.0] - 2026-08-23
9
+
10
+ Brings the SDK's typed surface up to date with Ollama v0.13.3+.
11
+
12
+ ### Added
13
+
14
+ - **`/api/create` field parity:** `CreateRequestOptions` now exposes the documented
15
+ `template`, `renderer`, `parser`, `license`, `system`, `parameters`, and `messages`
16
+ fields alongside the existing `from`/`files`/`adapters`/`quantize`. The old
17
+ `modelfile` string field is kept for backward compatibility but marked `@deprecated`
18
+ in favor of the discrete fields.
19
+ - **OpenAI Responses API bridge:** `client.openai.responses()` / `createResponses()`
20
+ POST to `/v1/responses`. Ollama implements this non-statefully, so
21
+ `previous_response_id`/`conversation` are typed but documented as ignored.
22
+ - **`reasoning_effort` / `reasoning.effort`:** Added to `OpenAIChatCompletionRequest`
23
+ for thinking models (`deepseek-r1`, `qwen3`, etc.), matching Ollama's OpenAI
24
+ compatibility docs.
25
+ - **`tool_choice` / `parallel_tool_calls`:** Now typed as accepted-but-ignored on
26
+ `OpenAIChatCompletionRequest` (previously omitted entirely), so passing a standard
27
+ OpenAI request object type-checks without modification.
28
+ - **Raw image bytes:** `Message.images` and `GenerateRequestOptions.images` accept
29
+ `Uint8Array` alongside base64 strings. `OllamaClient.chat()`/`generate()` encode any
30
+ `Uint8Array` entries to base64 automatically via the new `encodeImage` utility
31
+ (exported from the package root), using `Buffer` on Node and `btoa` elsewhere so it
32
+ stays Edge Runtime-safe.
33
+ - **`doneReason` in stream results:** `ChatStreamResult`/`GenerateStreamResult` now
34
+ carry `doneReason`, mapped from the final chunk's `done_reason` (e.g. `"stop"`,
35
+ `"load"`, `"unload"`).
36
+
37
+ ### Fixed
38
+
39
+ - `package.json`'s `homepage`, `repository`, and `bugs` URLs now point at
40
+ `ollama-sdk` instead of the pre-rename `ollama-client-ts`.
41
+
8
42
  ## [1.0.0] - 2026-08-17
9
43
 
10
44
  First public release. The version was `0.1.0` throughout development but was never
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # @nemesis-oss/ollama-sdk
2
2
 
3
- [![CI](https://github.com/shubhamtaywade82/ollama-client-ts/actions/workflows/ci.yml/badge.svg)](https://github.com/shubhamtaywade82/ollama-client-ts/actions/workflows/ci.yml)
3
+ [![CI](https://github.com/shubhamtaywade82/ollama-sdk/actions/workflows/ci.yml/badge.svg)](https://github.com/shubhamtaywade82/ollama-sdk/actions/workflows/ci.yml)
4
4
  [![npm version](https://img.shields.io/npm/v/@nemesis-oss/ollama-sdk.svg)](https://www.npmjs.com/package/@nemesis-oss/ollama-sdk)
5
5
  [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT)
6
6
 
@@ -11,12 +11,12 @@
11
11
  ## Key Features
12
12
 
13
13
  - 🚀 **Native Web Standards**: Built on native `fetch` and Web Streams. Zero external HTTP dependencies.
14
- - 🧠 **Reasoning & Thinking Tokens**: First-class support for reasoning models (`qwen3.5:2b`, `deepseek-r1`) with discrete `thinking` and `token` streaming events.
14
+ - 🧠 **Reasoning & Thinking Tokens**: First-class support for reasoning models (`qwen3:8b`, `deepseek-r1:8b`) with discrete `thinking` and `token` streaming events.
15
15
  - 🎯 **Zod-Powered Structured Outputs**: Strictly typed schema enforcement via `chatWithSchema` and `generateWithSchema` with resilient markdown JSON parsing.
16
16
  - 🛠️ **Autonomous Agent & Tool Calling**: Multi-turn agent loop (`Agent`) with automated tool execution, parameter validation, and self-correcting error recovery.
17
17
  - 🌐 **High Availability & Failover**: Multi-endpoint registry with priority routing, circuit breaker failover, and active health checks.
18
18
  - 🔌 **Model Context Protocol (MCP)**: Native adapters to convert MCP tools into Ollama-compatible function schemas.
19
- - 🌉 **OpenAI & Anthropic Compatibility Bridges**: Built-in clients for `/v1/chat/completions`, `/v1/models`, and `/v1/messages`.
19
+ - 🌉 **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.
20
20
  - 🌊 **Web Stream Adapters**: Drop-in adapters (`toTextStream`, `toDataStream`, `toResponse`) for Next.js Route Handlers and Vercel AI SDK.
21
21
  - 📈 **OpenTelemetry Instrumentation**: Automatic spans for HTTP requests, endpoint failover, chat/generate calls, and agent runs — zero-cost when OpenTelemetry isn't installed.
22
22
  - ⚡ **Edge Runtime Verified**: CI bundles and runs the client in a real Edge Runtime sandbox (Cloudflare Workers/Vercel Edge-compatible) with zero Node.js APIs.
@@ -45,7 +45,7 @@ const client = new OllamaClient();
45
45
 
46
46
  // Text helper
47
47
  const answer = await client.chatText({
48
- model: 'qwen3.5:2b',
48
+ model: 'qwen3:8b',
49
49
  messages: [{ role: 'user', content: 'Explain quantum computing in one sentence.' }],
50
50
  });
51
51
  console.log(answer);
@@ -55,7 +55,7 @@ console.log(answer);
55
55
 
56
56
  ```typescript
57
57
  const stream = await client.chatStream({
58
- model: 'qwen3.5:2b',
58
+ model: 'qwen3:8b',
59
59
  messages: [{ role: 'user', content: 'What is 18 * 4?' }],
60
60
  options: { temperature: 0 },
61
61
  });
@@ -86,7 +86,7 @@ const ProductSchema = z.object({
86
86
 
87
87
  const product = await client.chatWithSchema(
88
88
  {
89
- model: 'qwen3.5:2b',
89
+ model: 'qwen3:8b',
90
90
  messages: [{ role: 'user', content: 'Generate a gaming keyboard item.' }],
91
91
  },
92
92
  ProductSchema,
@@ -131,7 +131,7 @@ const registry = new ToolRegistry([weatherTool]);
131
131
  const agent = new Agent(client, { tools: registry, maxIterations: 5 });
132
132
 
133
133
  const response = await agent.run({
134
- model: 'qwen3.5:2b',
134
+ model: 'qwen3:8b',
135
135
  messages: [{ role: 'user', content: 'What is the weather in Tokyo?' }],
136
136
  });
137
137
 
@@ -191,7 +191,7 @@ import { toResponse } from '@nemesis-oss/ollama-sdk';
191
191
  export async function POST(req: Request) {
192
192
  const { messages } = await req.json();
193
193
  const stream = await client.chatStream({
194
- model: 'qwen3.5:2b',
194
+ model: 'qwen3:8b',
195
195
  messages,
196
196
  });
197
197
 
@@ -208,6 +208,13 @@ const openAIRes = await client.openai.chatCompletions({
208
208
  messages: [{ role: 'user', content: 'Hello via OpenAI bridge' }],
209
209
  });
210
210
 
211
+ // OpenAI Responses API endpoint (/v1/responses) — added in Ollama v0.13.3
212
+ const responsesRes = await client.openai.responses({
213
+ model: 'llama3.2',
214
+ input: 'Hello via the OpenAI Responses bridge',
215
+ });
216
+ console.log(responsesRes.output[0]?.content[0]?.text);
217
+
211
218
  // Anthropic compatibility endpoint (/v1/messages)
212
219
  const anthropicRes = await client.anthropic.messages({
213
220
  model: 'llama3.2',
@@ -215,6 +222,25 @@ const anthropicRes = await client.anthropic.messages({
215
222
  });
216
223
  ```
217
224
 
225
+ `/v1/responses` is implemented non-statefully by Ollama: send the full conversation in
226
+ `input` on every call — `previous_response_id` and `conversation` are accepted for
227
+ OpenAI request-shape compatibility but ignored (see `OpenAIResponsesRequest` JSDoc).
228
+
229
+ For thinking models (`deepseek-r1`, `qwen3`, etc.), both compatibility bridges' chat
230
+ completions request accept a reasoning effort knob:
231
+
232
+ ```typescript
233
+ await client.openai.chatCompletions({
234
+ model: 'deepseek-r1:8b',
235
+ messages: [{ role: 'user', content: 'Solve: 17 * 23' }],
236
+ reasoning_effort: 'high', // or `reasoning: { effort: 'high' }`
237
+ });
238
+ ```
239
+
240
+ `tool_choice` and `parallel_tool_calls` are also typed on the request so a standard
241
+ OpenAI request object type-checks unmodified, but Ollama's compat layer does not honor
242
+ either — see the `@remarks` on each field in `OpenAIChatCompletionRequest`.
243
+
218
244
  ### Multi-Endpoint High Availability Failover
219
245
 
220
246
  ```typescript
@@ -325,7 +351,7 @@ the original error via the standard `cause` property:
325
351
  import { OllamaClientError, OllamaRateLimitError } from '@nemesis-oss/ollama-sdk';
326
352
 
327
353
  try {
328
- await client.chatText({ model: 'qwen3.5:2b', messages: [...] });
354
+ await client.chatText({ model: 'qwen3:8b', messages: [...] });
329
355
  } catch (err) {
330
356
  if (err instanceof OllamaRateLimitError) {
331
357
  console.warn(`Rate limited, retry after ${err.retryAfterMs}ms`);
@@ -347,7 +373,7 @@ observe per-endpoint circuit state directly.
347
373
 
348
374
  ## Testing
349
375
 
350
- The test suite contains 105 automated tests across 4 testing tiers:
376
+ The test suite contains 114 automated tests across 4 testing tiers:
351
377
 
352
378
  ```bash
353
379
  # Run unit, integration, and functional test suite
@@ -28,7 +28,12 @@ interface ToolCall {
28
28
  interface Message {
29
29
  readonly role: Role;
30
30
  readonly content: string;
31
- readonly images?: readonly string[] | undefined;
31
+ /**
32
+ * Base64-encoded image strings, or raw `Uint8Array` image bytes. `Uint8Array` entries
33
+ * are base64-encoded automatically (see {@link encodeImage}) before the request reaches
34
+ * the wire — Ollama's REST API only ever accepts base64 strings.
35
+ */
36
+ readonly images?: readonly (string | Uint8Array)[] | undefined;
32
37
  readonly tool_calls?: readonly ToolCall[] | undefined;
33
38
  /** Set on a `role: 'tool'` message to identify which {@link ToolCall.id} this answers. */
34
39
  readonly tool_call_id?: string | undefined;
@@ -123,7 +128,12 @@ interface GenerateRequestOptions extends RequestCancellationOptions {
123
128
  readonly stream?: boolean | undefined;
124
129
  readonly raw?: boolean | undefined;
125
130
  readonly format?: FormatOption | undefined;
126
- readonly images?: readonly string[] | undefined;
131
+ /**
132
+ * Base64-encoded image strings, or raw `Uint8Array` image bytes. `Uint8Array` entries
133
+ * are base64-encoded automatically (see {@link encodeImage}) before the request reaches
134
+ * the wire — Ollama's REST API only ever accepts base64 strings.
135
+ */
136
+ readonly images?: readonly (string | Uint8Array)[] | undefined;
127
137
  readonly options?: ModelOptions | undefined;
128
138
  readonly keep_alive?: string | number | undefined;
129
139
  readonly think?: boolean | 'low' | 'medium' | 'high' | 'max' | undefined;
@@ -225,12 +235,29 @@ interface PushRequestOptions extends RequestCancellationOptions {
225
235
  }
226
236
  interface CreateRequestOptions extends RequestCancellationOptions {
227
237
  readonly model: string;
238
+ /**
239
+ * @deprecated Not part of the official `/api/create` REST payload — Ollama's server
240
+ * only ever accepted this in older client tooling that assembled a Modelfile string
241
+ * client-side. Use the discrete fields below instead (`from`, `files`, `adapters`,
242
+ * `template`, `system`, `parameters`, `license`, `messages`), which map 1:1 onto the
243
+ * documented Modelfile instructions and are validated server-side. Kept for backward
244
+ * compatibility with existing callers; not read by this SDK, but still forwarded as-is
245
+ * in the request body for any server/proxy that inspects it.
246
+ */
228
247
  readonly modelfile?: string | undefined;
229
248
  readonly stream?: boolean | undefined;
230
249
  readonly quantize?: string | undefined;
231
250
  readonly from?: string | undefined;
232
251
  readonly files?: Record<string, string> | undefined;
233
252
  readonly adapters?: Record<string, string> | undefined;
253
+ readonly template?: string | undefined;
254
+ readonly renderer?: string | undefined;
255
+ readonly parser?: string | undefined;
256
+ readonly license?: string | readonly string[] | undefined;
257
+ readonly system?: string | undefined;
258
+ /** Modelfile `PARAMETER` instructions, e.g. `{ temperature: 0.7, stop: ['\n'] }`. */
259
+ readonly parameters?: Record<string, unknown> | undefined;
260
+ readonly messages?: readonly Message[] | undefined;
234
261
  }
235
262
  interface DeleteRequestOptions extends RequestCancellationOptions {
236
263
  readonly model: string;
@@ -28,7 +28,12 @@ interface ToolCall {
28
28
  interface Message {
29
29
  readonly role: Role;
30
30
  readonly content: string;
31
- readonly images?: readonly string[] | undefined;
31
+ /**
32
+ * Base64-encoded image strings, or raw `Uint8Array` image bytes. `Uint8Array` entries
33
+ * are base64-encoded automatically (see {@link encodeImage}) before the request reaches
34
+ * the wire — Ollama's REST API only ever accepts base64 strings.
35
+ */
36
+ readonly images?: readonly (string | Uint8Array)[] | undefined;
32
37
  readonly tool_calls?: readonly ToolCall[] | undefined;
33
38
  /** Set on a `role: 'tool'` message to identify which {@link ToolCall.id} this answers. */
34
39
  readonly tool_call_id?: string | undefined;
@@ -123,7 +128,12 @@ interface GenerateRequestOptions extends RequestCancellationOptions {
123
128
  readonly stream?: boolean | undefined;
124
129
  readonly raw?: boolean | undefined;
125
130
  readonly format?: FormatOption | undefined;
126
- readonly images?: readonly string[] | undefined;
131
+ /**
132
+ * Base64-encoded image strings, or raw `Uint8Array` image bytes. `Uint8Array` entries
133
+ * are base64-encoded automatically (see {@link encodeImage}) before the request reaches
134
+ * the wire — Ollama's REST API only ever accepts base64 strings.
135
+ */
136
+ readonly images?: readonly (string | Uint8Array)[] | undefined;
127
137
  readonly options?: ModelOptions | undefined;
128
138
  readonly keep_alive?: string | number | undefined;
129
139
  readonly think?: boolean | 'low' | 'medium' | 'high' | 'max' | undefined;
@@ -225,12 +235,29 @@ interface PushRequestOptions extends RequestCancellationOptions {
225
235
  }
226
236
  interface CreateRequestOptions extends RequestCancellationOptions {
227
237
  readonly model: string;
238
+ /**
239
+ * @deprecated Not part of the official `/api/create` REST payload — Ollama's server
240
+ * only ever accepted this in older client tooling that assembled a Modelfile string
241
+ * client-side. Use the discrete fields below instead (`from`, `files`, `adapters`,
242
+ * `template`, `system`, `parameters`, `license`, `messages`), which map 1:1 onto the
243
+ * documented Modelfile instructions and are validated server-side. Kept for backward
244
+ * compatibility with existing callers; not read by this SDK, but still forwarded as-is
245
+ * in the request body for any server/proxy that inspects it.
246
+ */
228
247
  readonly modelfile?: string | undefined;
229
248
  readonly stream?: boolean | undefined;
230
249
  readonly quantize?: string | undefined;
231
250
  readonly from?: string | undefined;
232
251
  readonly files?: Record<string, string> | undefined;
233
252
  readonly adapters?: Record<string, string> | undefined;
253
+ readonly template?: string | undefined;
254
+ readonly renderer?: string | undefined;
255
+ readonly parser?: string | undefined;
256
+ readonly license?: string | readonly string[] | undefined;
257
+ readonly system?: string | undefined;
258
+ /** Modelfile `PARAMETER` instructions, e.g. `{ temperature: 0.7, stop: ['\n'] }`. */
259
+ readonly parameters?: Record<string, unknown> | undefined;
260
+ readonly messages?: readonly Message[] | undefined;
234
261
  }
235
262
  interface DeleteRequestOptions extends RequestCancellationOptions {
236
263
  readonly model: string;
package/dist/index.cjs CHANGED
@@ -822,6 +822,7 @@ function aggregateChat(accumulated, chunk) {
822
822
  },
823
823
  model: chunk.model,
824
824
  done: chunk.done,
825
+ doneReason: chunk.done ? chunk.done_reason : accumulated.doneReason,
825
826
  totalDurationMs: chunk.done && chunk.total_duration !== void 0 ? chunk.total_duration / NANOS_PER_MS : accumulated.totalDurationMs,
826
827
  usage: chunk.done ? extractUsage(chunk) : accumulated.usage,
827
828
  raw: chunk
@@ -871,6 +872,7 @@ function aggregateGenerate(accumulated, chunk) {
871
872
  response: accumulated.response + (chunk.response ?? ""),
872
873
  model: chunk.model,
873
874
  done: chunk.done,
875
+ doneReason: chunk.done ? chunk.done_reason : accumulated.doneReason,
874
876
  totalDurationMs: chunk.done && chunk.total_duration !== void 0 ? chunk.total_duration / NANOS_PER_MS : accumulated.totalDurationMs,
875
877
  usage: chunk.done ? extractUsage(chunk) : accumulated.usage,
876
878
  raw: chunk
@@ -1159,6 +1161,16 @@ var OpenAICompatClient = class {
1159
1161
  signal
1160
1162
  });
1161
1163
  }
1164
+ async createResponses(request, signal) {
1165
+ return this.http.request({
1166
+ path: "/v1/responses",
1167
+ body: request,
1168
+ signal
1169
+ });
1170
+ }
1171
+ async responses(request, signal) {
1172
+ return this.createResponses(request, signal);
1173
+ }
1162
1174
  };
1163
1175
 
1164
1176
  // src/integrations/anthropic.ts
@@ -1179,6 +1191,32 @@ var AnthropicCompatClient = class {
1179
1191
  }
1180
1192
  };
1181
1193
 
1194
+ // src/utils.ts
1195
+ async function encodeImage(image) {
1196
+ if (typeof image === "string") return image;
1197
+ const bufferCtor = globalThis.Buffer;
1198
+ if (bufferCtor !== void 0) return bufferCtor.from(image).toString("base64");
1199
+ const CHUNK_SIZE = 32768;
1200
+ let binary = "";
1201
+ for (let i = 0; i < image.length; i += CHUNK_SIZE) {
1202
+ binary += String.fromCharCode(...image.subarray(i, i + CHUNK_SIZE));
1203
+ }
1204
+ return btoa(binary);
1205
+ }
1206
+ async function encodeImages(images) {
1207
+ if (images === void 0) return void 0;
1208
+ return Promise.all(images.map((image) => encodeImage(image)));
1209
+ }
1210
+ async function withEncodedImages(req) {
1211
+ if (!req.images?.some((image) => image instanceof Uint8Array)) return req;
1212
+ return { ...req, images: await encodeImages(req.images) };
1213
+ }
1214
+ async function withEncodedMessageImages(messages) {
1215
+ if (!messages.some((message) => message.images?.some((image) => image instanceof Uint8Array)))
1216
+ return messages;
1217
+ return Promise.all(messages.map((message) => withEncodedImages(message)));
1218
+ }
1219
+
1182
1220
  // src/client.ts
1183
1221
  var OllamaClient = class {
1184
1222
  registry;
@@ -1264,12 +1302,13 @@ var OllamaClient = class {
1264
1302
  }
1265
1303
  }
1266
1304
  async chat(req) {
1305
+ const messages = await withEncodedMessageImages(req.messages);
1267
1306
  if (req.stream) {
1268
1307
  return this.executeWithFailover(async (http, signal) => {
1269
1308
  if (req.format !== void 0) this.assertStructuredOutputSupported(http.baseUrl, req.model);
1270
1309
  const stream = await http.requestStream({
1271
1310
  path: "/api/chat",
1272
- body: { ...req, stream: true },
1311
+ body: { ...req, messages, stream: true },
1273
1312
  signal
1274
1313
  });
1275
1314
  return normalizeChatStream(stream);
@@ -1288,7 +1327,7 @@ var OllamaClient = class {
1288
1327
  this.assertStructuredOutputSupported(http.baseUrl, req.model);
1289
1328
  return http.request({
1290
1329
  path: "/api/chat",
1291
- body: { ...req, stream: false },
1330
+ body: { ...req, messages, stream: false },
1292
1331
  signal
1293
1332
  });
1294
1333
  }, req);
@@ -1315,34 +1354,36 @@ var OllamaClient = class {
1315
1354
  return parseStructuredOutput(res.message.content, schema);
1316
1355
  }
1317
1356
  async generate(req) {
1318
- if (req.stream) {
1357
+ const encodedReq = await withEncodedImages(req);
1358
+ if (encodedReq.stream) {
1319
1359
  return this.executeWithFailover(async (http, signal) => {
1320
- if (req.format !== void 0) this.assertStructuredOutputSupported(http.baseUrl, req.model);
1360
+ if (encodedReq.format !== void 0)
1361
+ this.assertStructuredOutputSupported(http.baseUrl, encodedReq.model);
1321
1362
  const stream = await http.requestStream({
1322
1363
  path: "/api/generate",
1323
- body: { ...req, stream: true },
1364
+ body: { ...encodedReq, stream: true },
1324
1365
  signal
1325
1366
  });
1326
1367
  return normalizeGenerateStream(stream);
1327
- }, req);
1368
+ }, encodedReq);
1328
1369
  }
1329
1370
  return withSpan(
1330
- `text_completion ${req.model}`,
1371
+ `text_completion ${encodedReq.model}`,
1331
1372
  {
1332
1373
  [ATTR_GEN_AI_SYSTEM]: GEN_AI_SYSTEM_OLLAMA,
1333
1374
  [ATTR_GEN_AI_OPERATION_NAME]: "text_completion",
1334
- [ATTR_GEN_AI_REQUEST_MODEL]: req.model
1375
+ [ATTR_GEN_AI_REQUEST_MODEL]: encodedReq.model
1335
1376
  },
1336
1377
  async (span) => {
1337
1378
  const res = await this.executeWithFailover((http, signal) => {
1338
- if (req.format !== void 0)
1339
- this.assertStructuredOutputSupported(http.baseUrl, req.model);
1379
+ if (encodedReq.format !== void 0)
1380
+ this.assertStructuredOutputSupported(http.baseUrl, encodedReq.model);
1340
1381
  return http.request({
1341
1382
  path: "/api/generate",
1342
- body: { ...req, stream: false },
1383
+ body: { ...encodedReq, stream: false },
1343
1384
  signal
1344
1385
  });
1345
- }, req);
1386
+ }, encodedReq);
1346
1387
  span?.setAttributes({
1347
1388
  [ATTR_GEN_AI_RESPONSE_MODEL]: res.model,
1348
1389
  ...res.prompt_eval_count !== void 0 ? { [ATTR_GEN_AI_USAGE_INPUT_TOKENS]: res.prompt_eval_count } : {},
@@ -2026,6 +2067,7 @@ exports.createConsoleLogger = createConsoleLogger;
2026
2067
  exports.createTimeoutSignal = createTimeoutSignal;
2027
2068
  exports.defineTool = defineTool;
2028
2069
  exports.detectModelCapabilities = detectModelCapabilities;
2070
+ exports.encodeImage = encodeImage;
2029
2071
  exports.extractUsage = extractUsage;
2030
2072
  exports.inferRuntimeMode = inferRuntimeMode;
2031
2073
  exports.listAvailableModels = listAvailableModels;