@nemesis-oss/ollama-sdk 1.20.0 → 1.21.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/dist/index.d.cts CHANGED
@@ -230,6 +230,17 @@ interface ModelCapabilities {
230
230
  */
231
231
  readonly parallelToolCalls: ParallelToolCallBehavior;
232
232
  }
233
+ /**
234
+ * Classifies the server behind `baseUrl` from its hostname alone (no network call).
235
+ *
236
+ * - `cloud`: the hosted Ollama Cloud API (`ollama.com`, `*.ollama.com`). This is the only host
237
+ * known to reject structured output (`format`), so only it is refused pre-flight.
238
+ * - `local`: a private-network server — loopback, RFC 1918 / ULA addresses, `*.localhost`,
239
+ * `*.local`, `*.internal`, or a single-label name such as a Docker or Kubernetes service
240
+ * (`http://ollama:11434`).
241
+ * - `unknown`: any other hostname, including self-hosted servers on public domains. Those are
242
+ * not refused for `format`, and they are not assumed to be local either.
243
+ */
233
244
  declare function inferRuntimeMode(baseUrl: string): RuntimeMode;
234
245
  /**
235
246
  * Scans `/api/show`'s `model_info` for the model's **native** context length
@@ -2508,8 +2519,14 @@ declare class OllamaClient {
2508
2519
  *
2509
2520
  * For the low-level generated API (without the generic wrapper), use
2510
2521
  * `client.native.systemOne(request)` directly.
2522
+ *
2523
+ * @param options.signal Cancels the whole call, including the cloud-model
2524
+ * version probe that runs first, so the gate never extends a caller's
2525
+ * deadline. An abort rejects with an {@link OllamaAbortError}.
2511
2526
  */
2512
- systemOne<Q extends SystemOneQuestions = SystemOneQuestions>(request: SystemOneRequest<Q>): Promise<SystemOneResponse<Q>>;
2527
+ systemOne<Q extends SystemOneQuestions = SystemOneQuestions>(request: SystemOneRequest<Q>, options?: {
2528
+ readonly signal?: AbortSignal;
2529
+ }): Promise<SystemOneResponse<Q>>;
2513
2530
  /**
2514
2531
  * Cloud-model version gate for {@link systemOne} — throws
2515
2532
  * {@link OllamaCloudModelVersionError} when a cloud model is requested
package/dist/index.d.ts CHANGED
@@ -230,6 +230,17 @@ interface ModelCapabilities {
230
230
  */
231
231
  readonly parallelToolCalls: ParallelToolCallBehavior;
232
232
  }
233
+ /**
234
+ * Classifies the server behind `baseUrl` from its hostname alone (no network call).
235
+ *
236
+ * - `cloud`: the hosted Ollama Cloud API (`ollama.com`, `*.ollama.com`). This is the only host
237
+ * known to reject structured output (`format`), so only it is refused pre-flight.
238
+ * - `local`: a private-network server — loopback, RFC 1918 / ULA addresses, `*.localhost`,
239
+ * `*.local`, `*.internal`, or a single-label name such as a Docker or Kubernetes service
240
+ * (`http://ollama:11434`).
241
+ * - `unknown`: any other hostname, including self-hosted servers on public domains. Those are
242
+ * not refused for `format`, and they are not assumed to be local either.
243
+ */
233
244
  declare function inferRuntimeMode(baseUrl: string): RuntimeMode;
234
245
  /**
235
246
  * Scans `/api/show`'s `model_info` for the model's **native** context length
@@ -2508,8 +2519,14 @@ declare class OllamaClient {
2508
2519
  *
2509
2520
  * For the low-level generated API (without the generic wrapper), use
2510
2521
  * `client.native.systemOne(request)` directly.
2522
+ *
2523
+ * @param options.signal Cancels the whole call, including the cloud-model
2524
+ * version probe that runs first, so the gate never extends a caller's
2525
+ * deadline. An abort rejects with an {@link OllamaAbortError}.
2511
2526
  */
2512
- systemOne<Q extends SystemOneQuestions = SystemOneQuestions>(request: SystemOneRequest<Q>): Promise<SystemOneResponse<Q>>;
2527
+ systemOne<Q extends SystemOneQuestions = SystemOneQuestions>(request: SystemOneRequest<Q>, options?: {
2528
+ readonly signal?: AbortSignal;
2529
+ }): Promise<SystemOneResponse<Q>>;
2513
2530
  /**
2514
2531
  * Cloud-model version gate for {@link systemOne} — throws
2515
2532
  * {@link OllamaCloudModelVersionError} when a cloud model is requested
package/dist/index.js CHANGED
@@ -1606,16 +1606,23 @@ async function checkEndpointHealth(endpoint, fetchImpl, timeoutMs = 5e3, middlew
1606
1606
 
1607
1607
  // src/capabilities/capabilities.ts
1608
1608
  function inferRuntimeMode(baseUrl) {
1609
+ let host;
1609
1610
  try {
1610
- const url = new URL(baseUrl);
1611
- const host = url.hostname.toLowerCase().replace(/^\[|\]$/g, "");
1612
- if (host === "localhost" || host === "127.0.0.1" || host === "::1" || host.endsWith(".local") || host.startsWith("192.168.") || host.startsWith("10.")) {
1613
- return "local";
1614
- }
1615
- return "cloud";
1611
+ host = new URL(baseUrl).hostname.toLowerCase().replace(/^\[|\]$/g, "");
1616
1612
  } catch {
1617
1613
  return "unknown";
1618
1614
  }
1615
+ if (host === "ollama.com" || host.endsWith(".ollama.com")) return "cloud";
1616
+ if (isPrivateNetworkHost(host)) return "local";
1617
+ return "unknown";
1618
+ }
1619
+ function isPrivateNetworkHost(host) {
1620
+ if (!host.includes(".") && !host.includes(":")) return true;
1621
+ if (host.endsWith(".localhost") || host.endsWith(".local") || host.endsWith(".internal"))
1622
+ return true;
1623
+ if (host === "::1" || host === "0.0.0.0") return true;
1624
+ if (host.includes(":") && /^f[cd][0-9a-f]{0,2}:/.test(host)) return true;
1625
+ return /^(?:127|10)\.\d{1,3}\.\d{1,3}\.\d{1,3}$/.test(host) || /^192\.168\.\d{1,3}\.\d{1,3}$/.test(host) || /^172\.(?:1[6-9]|2\d|3[01])\.\d{1,3}\.\d{1,3}$/.test(host);
1619
1626
  }
1620
1627
  function extractContextLength(modelInfo) {
1621
1628
  if (!modelInfo) return void 0;
@@ -2617,6 +2624,24 @@ function createTimeoutSignal(timeoutMs, userSignal) {
2617
2624
  };
2618
2625
  return { signal: controller.signal, cancel };
2619
2626
  }
2627
+ function untilAborted(promise, signal, message) {
2628
+ if (signal === void 0) return promise;
2629
+ return new Promise((resolve, reject) => {
2630
+ const onAbort = () => reject(new OllamaAbortError(message));
2631
+ promise.then(
2632
+ (value) => {
2633
+ signal.removeEventListener("abort", onAbort);
2634
+ resolve(value);
2635
+ },
2636
+ (error) => {
2637
+ signal.removeEventListener("abort", onAbort);
2638
+ reject(error);
2639
+ }
2640
+ );
2641
+ if (signal.aborted) onAbort();
2642
+ else signal.addEventListener("abort", onAbort, { once: true });
2643
+ });
2644
+ }
2620
2645
 
2621
2646
  // src/keep-alive.ts
2622
2647
  var KEEP_ALIVE_UNLOAD = 0;
@@ -5644,15 +5669,13 @@ var FailoverHttpClient = class {
5644
5669
  async request(options) {
5645
5670
  const { model, ...httpOptions } = options;
5646
5671
  return this.client.executeWithFailover(
5647
- async (http, signal) => {
5648
- const mergedOptions = {
5649
- ...httpOptions,
5650
- ...httpOptions.signal !== void 0 ? { signal } : { signal }
5651
- };
5652
- return http.request(mergedOptions);
5653
- },
5672
+ // The signal handed to the operation already combines the caller's signal
5673
+ // (passed below) with the failover scope and timeout. Using it, rather than
5674
+ // the caller's, keeps the request cancellable by `destroy()` as well.
5675
+ async (http, signal) => http.request({ ...httpOptions, signal }),
5654
5676
  {
5655
5677
  singleEndpoint: false,
5678
+ ...httpOptions.signal !== void 0 ? { signal: httpOptions.signal } : {},
5656
5679
  ...model !== void 0 ? { model } : {}
5657
5680
  }
5658
5681
  );
@@ -6492,12 +6515,18 @@ var OllamaClient = class _OllamaClient {
6492
6515
  *
6493
6516
  * For the low-level generated API (without the generic wrapper), use
6494
6517
  * `client.native.systemOne(request)` directly.
6518
+ *
6519
+ * @param options.signal Cancels the whole call, including the cloud-model
6520
+ * version probe that runs first, so the gate never extends a caller's
6521
+ * deadline. An abort rejects with an {@link OllamaAbortError}.
6495
6522
  */
6496
- async systemOne(request) {
6497
- await this.assertCloudModelSupported(request.model);
6498
- return this.native.systemOne(
6499
- request
6500
- );
6523
+ async systemOne(request, options = {}) {
6524
+ await this.assertCloudModelSupported(request.model, options.signal);
6525
+ return this.runtime.invoke({
6526
+ operation: systemOneOp,
6527
+ body: request,
6528
+ ...options.signal !== void 0 ? { signal: options.signal } : {}
6529
+ });
6501
6530
  }
6502
6531
  /**
6503
6532
  * Cloud-model version gate for {@link systemOne} — throws
@@ -6509,19 +6538,25 @@ var OllamaClient = class _OllamaClient {
6509
6538
  * so a later gated call retries), so at most one extra round-trip happens
6510
6539
  * across all gated calls.
6511
6540
  */
6512
- async assertCloudModelSupported(model) {
6541
+ async assertCloudModelSupported(model, signal) {
6513
6542
  if (!isCloudModel(model)) return;
6514
6543
  const candidates = this.registry.candidates();
6515
6544
  const baseUrl = candidates[0]?.baseUrl ?? DEFAULT_BASE_URL;
6516
6545
  if (inferRuntimeMode(baseUrl) !== "local") return;
6546
+ if (this.serverVersionCache === void 0) {
6547
+ this.serverVersionCache = this.native.version().then((v) => v.version);
6548
+ }
6549
+ const probe = this.serverVersionCache;
6517
6550
  let version;
6518
6551
  try {
6519
- if (this.serverVersionCache === void 0) {
6520
- this.serverVersionCache = this.native.version().then((v) => v.version);
6521
- }
6522
- version = await this.serverVersionCache;
6523
- } catch {
6524
- this.serverVersionCache = void 0;
6552
+ version = await untilAborted(
6553
+ probe,
6554
+ signal,
6555
+ "System One call aborted while probing the Ollama server version"
6556
+ );
6557
+ } catch (error) {
6558
+ if (signal?.aborted) throw error;
6559
+ if (this.serverVersionCache === probe) this.serverVersionCache = void 0;
6525
6560
  return;
6526
6561
  }
6527
6562
  if (version && compareOllamaVersions(version, CLOUD_MODEL_MIN_OLLAMA_VERSION) < 0) {