@nemesis-oss/ollama-sdk 1.20.0 → 1.20.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/CHANGELOG.md CHANGED
@@ -1,5 +1,32 @@
1
1
  # Changelog
2
2
 
3
+ ## [1.20.1] — Self-hosted servers are no longer refused as Ollama Cloud
4
+
5
+ Structured output (`format`) was refused pre-flight for every base URL outside `localhost`, `*.local`, `192.168.*` and `10.*`. The classifier treated any other host as cloud, so a self-hosted server reached by a Docker or Kubernetes service name (`http://ollama:11434`), a `172.16/12` address, or a public domain of its own threw `OllamaUnsupportedCapabilityError` on every `format` request. There was no opt-out. Caught while wiring `@nemesis-oss/ollama-sdk` into agentic-tui, whose docker-compose stack points the supervisor at `http://ollama:11434`.
6
+
7
+ ### The classification rule
8
+
9
+ `inferRuntimeMode(baseUrl)` now classifies by hostname alone, with three outcomes:
10
+
11
+ - **`cloud`**: `ollama.com` and `*.ollama.com` only. A suffix lookalike such as `ollama.com.evil.net` is not cloud. Only this host is refused for `format`.
12
+ - **`local`**: a private-network server. That covers loopback (`127.0.0.0/8`, `::1`, `0.0.0.0`, `*.localhost`), RFC 1918 IPv4 (`10/8`, `172.16/12`, `192.168/16`), IPv6 unique-local (`fc00::/7`), `*.local`, `*.internal`, and single-label names with no dot or colon, such as Docker or Kubernetes service names.
13
+ - **`unknown`**: every other hostname, including self-hosted servers on public domains. These are not refused for `format`, and they are not assumed to be local.
14
+
15
+ The IPv4 checks match address literals only, so `10.example.com` is no longer mistaken for `10.0.0.0/8`.
16
+
17
+ ### Behavior changes
18
+
19
+ - `format` requests go to self-hosted servers on any host. Only `ollama.com` is refused.
20
+ - `parallelToolCalls` for `local` hosts uses the local family and name heuristics. For `unknown` hosts it reports `'unknown'`. Previously every non-private host got the cloud heuristic, which reported `'yes'` for any tool-capable model.
21
+ - The cloud-model version gate (`OllamaCloudModelVersionError`) probes `/api/version` only for `local` endpoints. Previously a self-hosted server with a non-private hostname skipped the gate. It now still skips it when the hostname is `unknown`.
22
+ - `runtimeMode()` can now return `'unknown'`, as it already could for an unparseable base URL.
23
+
24
+ ### Tests
25
+
26
+ - `test/unit-capabilities.test.ts`: classification table for each outcome, including the lookalike and address-literal edge cases.
27
+ - `test/parallel-tool-calls.test.ts`: a public self-hosted host reports `parallelToolCalls: 'unknown'` (previously `'yes'`).
28
+ - `test/unsupported-capability.test.ts`: `format` requests reach a self-hosted endpoint on a Docker service name and on a public domain.
29
+
3
30
  ## [1.20.0] — Post-audit v1.19.1 refresh: re-pin 36a6e99, usage model grouping, ShowResponse prompt provenance, the 0.40.5 cloud-model gate
4
31
 
5
32
  The daily audit against the v1.19.1 baseline claimed "zero upstream drift since d9b8601, no action needed"; independent verification (raw content diff of upstream `docs/openapi.yaml` against the pin — the same discipline that caught the v1.19.0 audit's false row) found the headline false and two repository-internal defects besides. Two upstream commits had landed after the 2026-10-16 pin: `2476d5e` adds model grouping to cloud usage, and `36a6e99` populates `ShowResponse.renderer/parser` and adds `template_source`. Separately: the docs had promised since 1.19.0 that cloud models (`clef:cloud`) require "Ollama >= 0.40.5, enforced automatically" while only the blanket 0.35.0 floor was ever enforced, and `docs-index.json`'s `pinnedSha` silently lagged the pin by one refresh (nothing consumed it, no test covered it). All changes additive or gap-closing; no breaking changes (see [ADR 0036](./docs/adr/0036-post-audit-v1191-refresh-usage-grouping-cloud-gate.md)). 1087 pre-existing tests stay green, 14 net-new tests added.
package/dist/index.cjs CHANGED
@@ -1608,16 +1608,23 @@ async function checkEndpointHealth(endpoint, fetchImpl, timeoutMs = 5e3, middlew
1608
1608
 
1609
1609
  // src/capabilities/capabilities.ts
1610
1610
  function inferRuntimeMode(baseUrl) {
1611
+ let host;
1611
1612
  try {
1612
- const url = new URL(baseUrl);
1613
- const host = url.hostname.toLowerCase().replace(/^\[|\]$/g, "");
1614
- if (host === "localhost" || host === "127.0.0.1" || host === "::1" || host.endsWith(".local") || host.startsWith("192.168.") || host.startsWith("10.")) {
1615
- return "local";
1616
- }
1617
- return "cloud";
1613
+ host = new URL(baseUrl).hostname.toLowerCase().replace(/^\[|\]$/g, "");
1618
1614
  } catch {
1619
1615
  return "unknown";
1620
1616
  }
1617
+ if (host === "ollama.com" || host.endsWith(".ollama.com")) return "cloud";
1618
+ if (isPrivateNetworkHost(host)) return "local";
1619
+ return "unknown";
1620
+ }
1621
+ function isPrivateNetworkHost(host) {
1622
+ if (!host.includes(".") && !host.includes(":")) return true;
1623
+ if (host.endsWith(".localhost") || host.endsWith(".local") || host.endsWith(".internal"))
1624
+ return true;
1625
+ if (host === "::1" || host === "0.0.0.0") return true;
1626
+ if (host.includes(":") && /^f[cd][0-9a-f]{0,2}:/.test(host)) return true;
1627
+ 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);
1621
1628
  }
1622
1629
  function extractContextLength(modelInfo) {
1623
1630
  if (!modelInfo) return void 0;