@nemesis-oss/ollama-sdk 1.7.0 → 1.9.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.
Files changed (46) hide show
  1. package/CHANGELOG.md +168 -1
  2. package/README.md +181 -10
  3. package/dist/anthropic-5oYIRS6D.d.ts +1649 -0
  4. package/dist/anthropic-BIh45UXj.d.cts +1649 -0
  5. package/dist/{compose-DLStUfaF.d.ts → compose-DCVYMvOL.d.ts} +1 -1
  6. package/dist/{compose-DS-UWFx8.d.cts → compose-Dj6FdF2n.d.cts} +1 -1
  7. package/dist/generated-api.cjs +116 -9
  8. package/dist/generated-api.cjs.map +1 -1
  9. package/dist/generated-api.d.cts +321 -630
  10. package/dist/generated-api.d.ts +321 -630
  11. package/dist/generated-api.js +112 -10
  12. package/dist/generated-api.js.map +1 -1
  13. package/dist/generated-models-schemas.cjs +151 -3
  14. package/dist/generated-models-schemas.cjs.map +1 -1
  15. package/dist/generated-models-schemas.d.cts +228 -3
  16. package/dist/generated-models-schemas.d.ts +228 -3
  17. package/dist/generated-models-schemas.js +134 -4
  18. package/dist/generated-models-schemas.js.map +1 -1
  19. package/dist/generated-runtime.cjs +679 -60
  20. package/dist/generated-runtime.cjs.map +1 -1
  21. package/dist/generated-runtime.d.cts +22 -24
  22. package/dist/generated-runtime.d.ts +22 -24
  23. package/dist/generated-runtime.js +678 -61
  24. package/dist/generated-runtime.js.map +1 -1
  25. package/dist/index.cjs +2468 -132
  26. package/dist/index.cjs.map +1 -1
  27. package/dist/index.d.cts +1666 -864
  28. package/dist/index.d.ts +1666 -864
  29. package/dist/index.js +2432 -133
  30. package/dist/index.js.map +1 -1
  31. package/dist/mcp/tools.json +306 -73
  32. package/dist/mcp-generated.cjs +138 -7
  33. package/dist/mcp-generated.cjs.map +1 -1
  34. package/dist/mcp-generated.d.cts +2 -2
  35. package/dist/mcp-generated.d.ts +2 -2
  36. package/dist/mcp-generated.js +138 -7
  37. package/dist/mcp-generated.js.map +1 -1
  38. package/dist/{runtime-qwD72cpn.d.ts → runtime-C3rfpkZw.d.ts} +286 -5
  39. package/dist/{runtime-bQ48I-RC.d.cts → runtime-D2W9TfCJ.d.cts} +286 -5
  40. package/dist/skills.cjs.map +1 -1
  41. package/dist/skills.d.cts +3 -3
  42. package/dist/skills.d.ts +3 -3
  43. package/dist/skills.js.map +1 -1
  44. package/dist/{types-5PAjLoCp.d.cts → types-DVcX_zMp.d.cts} +110 -10
  45. package/dist/{types-5PAjLoCp.d.ts → types-DVcX_zMp.d.ts} +110 -10
  46. package/package.json +9 -5
package/dist/index.cjs CHANGED
@@ -48,6 +48,47 @@ var __callDispose = (stack, error, hasError) => {
48
48
  return next();
49
49
  };
50
50
 
51
+ // src/transport/host.ts
52
+ var DEFAULT_PORT = "11434";
53
+ var SCHEME_RE = /^[a-z][a-z0-9+.-]*:\/\//i;
54
+ function normalizeBaseUrl(host) {
55
+ const trimmed = host.trim();
56
+ if (trimmed === "") return trimmed;
57
+ const implicitScheme = !SCHEME_RE.test(trimmed);
58
+ const withHost = trimmed.startsWith(":") ? `127.0.0.1${trimmed}` : trimmed;
59
+ const candidate = implicitScheme ? `http://${withHost}` : withHost;
60
+ let url;
61
+ try {
62
+ url = new URL(candidate);
63
+ } catch {
64
+ return trimmed.replace(/\/+$/, "");
65
+ }
66
+ let protocol = url.protocol;
67
+ let port = url.port;
68
+ if (implicitScheme) {
69
+ if (port === "443") {
70
+ protocol = "https:";
71
+ port = "";
72
+ } else if (port === "") {
73
+ port = DEFAULT_PORT;
74
+ }
75
+ }
76
+ const authority = `${url.hostname}${port !== "" ? `:${port}` : ""}`;
77
+ return `${protocol}//${authority}${url.pathname}`.replace(/\/+$/, "");
78
+ }
79
+ function isEnvApiKeyHost(baseUrl) {
80
+ let hostname;
81
+ try {
82
+ hostname = new URL(normalizeBaseUrl(baseUrl)).hostname.toLowerCase();
83
+ } catch {
84
+ return false;
85
+ }
86
+ if (hostname === "ollama.com" || hostname.endsWith(".ollama.com")) return true;
87
+ if (hostname === "localhost" || hostname.endsWith(".localhost")) return true;
88
+ if (hostname === "[::1]" || hostname === "::1" || hostname === "0.0.0.0") return true;
89
+ return /^127(?:\.\d{1,3}){3}$/.test(hostname);
90
+ }
91
+
51
92
  // src/config.ts
52
93
  var DEFAULT_BASE_URL = "http://localhost:11434";
53
94
  var DEFAULT_TIMEOUT_MS = 3e4;
@@ -56,11 +97,16 @@ function readEnv(name) {
56
97
  return typeof process !== "undefined" && process.env ? process.env[name] : void 0;
57
98
  }
58
99
  function resolveBaseUrl(configBaseUrl) {
59
- return configBaseUrl ?? readEnv("OLLAMA_HOST") ?? DEFAULT_BASE_URL;
100
+ return normalizeBaseUrl(configBaseUrl ?? readEnv("OLLAMA_HOST") ?? DEFAULT_BASE_URL);
60
101
  }
61
102
  function resolveApiKey(configApiKey) {
62
103
  return configApiKey ?? readEnv("OLLAMA_API_KEY");
63
104
  }
105
+ function resolveEndpointApiKey(configApiKey, baseUrl) {
106
+ if (configApiKey !== void 0) return configApiKey;
107
+ const envKey = readEnv("OLLAMA_API_KEY");
108
+ return envKey !== void 0 && isEnvApiKeyHost(baseUrl) ? envKey : void 0;
109
+ }
64
110
  function resolveCredentialEndpoints(config) {
65
111
  if (config.credentials === void 0) return [];
66
112
  const modelsByCredential = /* @__PURE__ */ new Map();
@@ -93,6 +139,18 @@ var DEFAULT_FAILOVER_CODES = [
93
139
  "network_error",
94
140
  "timeout",
95
141
  "server_error",
142
+ // HTTP 503 — server queue saturated (OLLAMA_MAX_QUEUE exceeded).
143
+ // The new OllamaOverloadedError specialization is retryable: the
144
+ // queue will eventually drain, and a different candidate endpoint
145
+ // may have capacity right now. Listed separately from
146
+ // 'server_error' because OllamaOverloadedError extends
147
+ // OllamaClientError (NOT OllamaServerError) — see
148
+ // src/errors.ts for the rationale.
149
+ "overloaded",
150
+ // HTTP 502 — cloud model could not be reached by the Ollama Cloud
151
+ // proxy. Fail over to a different model/endpoint rather than
152
+ // retrying the same one.
153
+ "bad_gateway",
96
154
  "rate_limited",
97
155
  "auth_error",
98
156
  // A candidate endpoint rejected the request pre-flight for a known-unsupported
@@ -161,6 +219,24 @@ var OllamaServerError = class extends OllamaClientError {
161
219
  super(message, { ...options, code: "server_error", retryable: true });
162
220
  }
163
221
  };
222
+ var OllamaOverloadedError = class extends OllamaClientError {
223
+ constructor(message, options) {
224
+ super(message, { ...options, code: "overloaded", status: 503, retryable: true });
225
+ }
226
+ };
227
+ var OllamaBadGatewayError = class extends OllamaClientError {
228
+ constructor(message, options) {
229
+ super(message, { ...options, code: "bad_gateway", status: 502, retryable: true });
230
+ }
231
+ };
232
+ var OllamaStreamError = class extends OllamaClientError {
233
+ /** Content accumulated before the in-band error frame, or '' if none. */
234
+ partialContent;
235
+ constructor(message, options) {
236
+ super(message, { ...options, code: "stream_error", retryable: false });
237
+ this.partialContent = options?.partialContent ?? "";
238
+ }
239
+ };
164
240
  var OllamaAbortError = class extends OllamaClientError {
165
241
  constructor(message, options) {
166
242
  super(message, { ...options, code: "aborted", retryable: false });
@@ -184,6 +260,35 @@ var OllamaRequestValidationError = class extends OllamaClientError {
184
260
  this.issues = options.issues;
185
261
  }
186
262
  };
263
+ var OllamaRequestTooLargeError = class extends OllamaClientError {
264
+ operationId;
265
+ actualBytes;
266
+ maxBytes;
267
+ constructor(message, options) {
268
+ super(message, { ...options, code: "request_too_large", retryable: false, status: 413 });
269
+ this.operationId = options.operationId;
270
+ this.actualBytes = options.actualBytes;
271
+ this.maxBytes = options.maxBytes;
272
+ }
273
+ };
274
+ var OllamaResponseValidationError = class extends OllamaClientError {
275
+ operationId;
276
+ issues;
277
+ constructor(message, options) {
278
+ super(message, { ...options, code: "response_validation_error", retryable: false });
279
+ this.operationId = options.operationId;
280
+ this.issues = options.issues;
281
+ }
282
+ };
283
+ var OllamaServerVersionUnknownError = class extends OllamaClientError {
284
+ operationId;
285
+ minRequiredVersion;
286
+ constructor(message, options) {
287
+ super(message, { ...options, code: "server_version_unknown", retryable: false });
288
+ this.operationId = options.operationId;
289
+ this.minRequiredVersion = options.minRequiredVersion;
290
+ }
291
+ };
187
292
  var OllamaToolTimeoutError = class extends OllamaClientError {
188
293
  toolName;
189
294
  timeoutMs;
@@ -281,6 +386,12 @@ function statusToError(status, message, options) {
281
386
  if (status === 429) {
282
387
  return new OllamaRateLimitError(message, { ...options, status });
283
388
  }
389
+ if (status === 502) {
390
+ return new OllamaBadGatewayError(message, { ...options });
391
+ }
392
+ if (status === 503) {
393
+ return new OllamaOverloadedError(message, { ...options });
394
+ }
284
395
  if (status >= 500) {
285
396
  return new OllamaServerError(message, { ...options, status });
286
397
  }
@@ -551,6 +662,75 @@ function composeMiddleware(middlewares, finalHandler) {
551
662
  };
552
663
  }
553
664
 
665
+ // src/vision.ts
666
+ var DATA_URI_PATTERN = /^data:image\/[a-z0-9.+-]+;base64,/i;
667
+ var IMAGE_FILE_EXTENSION_PATTERN = /\.(png|jpe?g|webp|gif|bmp|tiff?|avif)$/i;
668
+ function imageStringNeedsResolution(input) {
669
+ return DATA_URI_PATTERN.test(input) || input.startsWith("http://") || input.startsWith("https://") || IMAGE_FILE_EXTENSION_PATTERN.test(input);
670
+ }
671
+ function hasNodeFs() {
672
+ return typeof process !== "undefined" && process !== null && typeof process.versions?.node === "string";
673
+ }
674
+ var NODE_FS_MODULE = "node:fs";
675
+ async function readFileAsBase64(path) {
676
+ const { promises: fs } = await import(NODE_FS_MODULE);
677
+ const data = await fs.readFile(path);
678
+ return data.toString("base64");
679
+ }
680
+ async function fetchUrlAsBase64(url, signal) {
681
+ const res = await fetch(url, signal !== void 0 ? { signal } : void 0);
682
+ if (!res.ok) {
683
+ throw new OllamaClientError(
684
+ `Failed to fetch image from URL "${url}": HTTP ${res.status} ${res.statusText}.`,
685
+ { code: "image_fetch_failed" }
686
+ );
687
+ }
688
+ const arrayBuffer = await res.arrayBuffer();
689
+ return encodeBytesBase64(new Uint8Array(arrayBuffer));
690
+ }
691
+ function encodeBytesBase64(bytes) {
692
+ const bufferCtor = globalThis.Buffer;
693
+ if (bufferCtor !== void 0) return bufferCtor.from(bytes).toString("base64");
694
+ const CHUNK_SIZE = 32768;
695
+ let binary = "";
696
+ for (let i = 0; i < bytes.length; i += CHUNK_SIZE) {
697
+ binary += String.fromCharCode(...bytes.subarray(i, i + CHUNK_SIZE));
698
+ }
699
+ return btoa(binary);
700
+ }
701
+ async function resolveImageInput(input, signal) {
702
+ if (typeof input !== "string") {
703
+ return encodeBytesBase64(input instanceof Uint8Array ? input : new Uint8Array(input));
704
+ }
705
+ if (DATA_URI_PATTERN.test(input)) {
706
+ return input.replace(DATA_URI_PATTERN, "").trim();
707
+ }
708
+ if (input.startsWith("http://") || input.startsWith("https://")) {
709
+ return fetchUrlAsBase64(input, signal);
710
+ }
711
+ if (IMAGE_FILE_EXTENSION_PATTERN.test(input)) {
712
+ if (!hasNodeFs()) {
713
+ throw new OllamaClientError(
714
+ `Vision input "${input}" looks like a local file path, but this runtime has no Node.js \`fs\` module (browser/edge). Read the file into bytes yourself and pass the \`Uint8Array\`, or pass a base64 string / data URI / http(s) URL instead.`,
715
+ { code: "vision_path_unsupported_runtime" }
716
+ );
717
+ }
718
+ try {
719
+ return await readFileAsBase64(input);
720
+ } catch {
721
+ return input;
722
+ }
723
+ }
724
+ return input.trim();
725
+ }
726
+ async function resolveImages(images, signal) {
727
+ if (images === void 0) return void 0;
728
+ if (images.every((image) => typeof image === "string" && !imageStringNeedsResolution(image))) {
729
+ return images;
730
+ }
731
+ return Promise.all(images.map((image) => resolveImageInput(image, signal)));
732
+ }
733
+
554
734
  // src/utils.ts
555
735
  async function encodeImage(image) {
556
736
  if (typeof image === "string") return image;
@@ -563,18 +743,22 @@ async function encodeImage(image) {
563
743
  }
564
744
  return btoa(binary);
565
745
  }
566
- async function encodeImages(images) {
567
- if (images === void 0) return void 0;
568
- return Promise.all(images.map((image) => encodeImage(image)));
746
+ async function encodeImages(images, signal) {
747
+ return resolveImages(images, signal);
569
748
  }
570
- async function withEncodedImages(req) {
571
- if (!req.images?.some((image) => image instanceof Uint8Array)) return req;
572
- return { ...req, images: await encodeImages(req.images) };
749
+ async function withEncodedImages(req, signal) {
750
+ const needsResolution = req.images?.some(
751
+ (image) => typeof image !== "string" || imageStringNeedsResolution(image)
752
+ );
753
+ if (!needsResolution) return req;
754
+ return { ...req, images: await encodeImages(req.images ?? [], signal) };
573
755
  }
574
- async function withEncodedMessageImages(messages) {
575
- if (!messages.some((message) => message.images?.some((image) => image instanceof Uint8Array)))
576
- return messages;
577
- return Promise.all(messages.map((message) => withEncodedImages(message)));
756
+ async function withEncodedMessageImages(messages, signal) {
757
+ const needsResolution = messages.some(
758
+ (message) => message.images?.some((image) => typeof image !== "string" || imageStringNeedsResolution(image))
759
+ );
760
+ if (!needsResolution) return messages;
761
+ return Promise.all(messages.map((message) => withEncodedImages(message, signal)));
578
762
  }
579
763
  function disposableTimer(timer) {
580
764
  return {
@@ -603,6 +787,12 @@ function disposableSpan(span) {
603
787
  }
604
788
 
605
789
  // src/streaming/ndjson.ts
790
+ function isInBandErrorFrame(value) {
791
+ if (typeof value !== "object" || value === null) return false;
792
+ const record = value;
793
+ if (typeof record.error !== "string" || record.error.length === 0) return false;
794
+ return !("model" in record) && !("response" in record) && !("message" in record) && !("status" in record) && !("done" in record);
795
+ }
606
796
  async function* parseNdjsonStream(stream) {
607
797
  var _stack = [];
608
798
  try {
@@ -619,17 +809,34 @@ async function* parseNdjsonStream(stream) {
619
809
  for (const line of lines) {
620
810
  const trimmed = line.trim();
621
811
  if (!trimmed) continue;
812
+ let json;
622
813
  try {
623
- yield JSON.parse(trimmed);
814
+ json = JSON.parse(trimmed);
624
815
  } catch (err) {
625
816
  throw new OllamaGenericClientError(`Failed to parse stream JSON chunk: ${trimmed}`, {
626
817
  cause: err
627
818
  });
628
819
  }
820
+ if (isInBandErrorFrame(json)) {
821
+ throw new OllamaStreamError(json.error);
822
+ }
823
+ yield json;
629
824
  }
630
825
  }
631
826
  if (buffer.trim()) {
632
- yield JSON.parse(buffer.trim());
827
+ const trimmed = buffer.trim();
828
+ let json;
829
+ try {
830
+ json = JSON.parse(trimmed);
831
+ } catch (err) {
832
+ throw new OllamaGenericClientError(`Failed to parse stream JSON chunk: ${trimmed}`, {
833
+ cause: err
834
+ });
835
+ }
836
+ if (isInBandErrorFrame(json)) {
837
+ throw new OllamaStreamError(json.error);
838
+ }
839
+ yield json;
633
840
  }
634
841
  } catch (_) {
635
842
  var _error = _, _hasError = true;
@@ -840,7 +1047,7 @@ var HttpClient = class {
840
1047
  onLifecycleEvent;
841
1048
  requestId;
842
1049
  constructor(options) {
843
- this.baseUrl = options.baseUrl.replace(/\/+$/, "");
1050
+ this.baseUrl = normalizeBaseUrl(options.baseUrl);
844
1051
  this.apiKey = options.apiKey;
845
1052
  this.defaultHeaders = options.headers ?? {};
846
1053
  this.fetchImpl = options.fetch ?? globalThis.fetch;
@@ -1229,7 +1436,7 @@ async function checkEndpointHealth(endpoint, fetchImpl, timeoutMs = 5e3, middlew
1229
1436
  function inferRuntimeMode(baseUrl) {
1230
1437
  try {
1231
1438
  const url = new URL(baseUrl);
1232
- const host = url.hostname.toLowerCase();
1439
+ const host = url.hostname.toLowerCase().replace(/^\[|\]$/g, "");
1233
1440
  if (host === "localhost" || host === "127.0.0.1" || host === "::1" || host.endsWith(".local") || host.startsWith("192.168.") || host.startsWith("10.")) {
1234
1441
  return "local";
1235
1442
  }
@@ -1247,6 +1454,63 @@ function extractContextLength(modelInfo) {
1247
1454
  }
1248
1455
  return void 0;
1249
1456
  }
1457
+ function extractModelFamily(showRes) {
1458
+ if (showRes.model_info) {
1459
+ for (const [key, value] of Object.entries(showRes.model_info)) {
1460
+ if (key.endsWith(".architecture") && typeof value === "string" && value.length > 0) {
1461
+ return value.toLowerCase();
1462
+ }
1463
+ }
1464
+ }
1465
+ if (showRes.details?.family && showRes.details.family.length > 0) {
1466
+ return showRes.details.family.toLowerCase();
1467
+ }
1468
+ const families = showRes.details?.families;
1469
+ if (families && families.length > 0) {
1470
+ const first = families[0];
1471
+ if (typeof first === "string" && first.length > 0) return first.toLowerCase();
1472
+ }
1473
+ return void 0;
1474
+ }
1475
+ var PARALLEL_TOOL_CALL_LOCAL_FAMILIES = /* @__PURE__ */ new Set([
1476
+ "qwen2",
1477
+ "qwen2.5",
1478
+ "qwen3",
1479
+ "llama3.1",
1480
+ "llama3.2",
1481
+ "llama3.3",
1482
+ "llama4",
1483
+ "mistral",
1484
+ "mixtral",
1485
+ "hermes",
1486
+ "command-r",
1487
+ "command-r-plus"
1488
+ ]);
1489
+ var PARALLEL_TOOL_CALL_NAME_HINTS = /* @__PURE__ */ new Set([
1490
+ "tool-use",
1491
+ "tooluse",
1492
+ "function-call",
1493
+ "functioncall",
1494
+ "instruct",
1495
+ // Most "instruct" variants of tool-capable families ship with parallel tool calls
1496
+ "hermes",
1497
+ "command-r"
1498
+ ]);
1499
+ function inferLocalParallelToolCalls(model, family) {
1500
+ const modelLower = model.toLowerCase();
1501
+ if (family !== void 0 && PARALLEL_TOOL_CALL_LOCAL_FAMILIES.has(family)) {
1502
+ return "yes";
1503
+ }
1504
+ for (const hint of PARALLEL_TOOL_CALL_NAME_HINTS) {
1505
+ if (modelLower.includes(hint)) {
1506
+ return "yes";
1507
+ }
1508
+ }
1509
+ return "unknown";
1510
+ }
1511
+ function inferCloudParallelToolCalls(supportsTools) {
1512
+ return supportsTools ? "yes" : "unknown";
1513
+ }
1250
1514
  async function detectModelCapabilities(http, model, signal) {
1251
1515
  const showRes = await http.request({
1252
1516
  path: "/api/show",
@@ -1256,10 +1520,16 @@ async function detectModelCapabilities(http, model, signal) {
1256
1520
  const reported = showRes.capabilities ?? [];
1257
1521
  const reportedSet = new Set(reported.map((c) => c.toLowerCase()));
1258
1522
  const contextLength = extractContextLength(showRes.model_info);
1523
+ const supportsTools = reportedSet.has("tools");
1524
+ const runtimeMode = inferRuntimeMode(http.baseUrl);
1525
+ const parallelToolCalls = runtimeMode === "cloud" ? inferCloudParallelToolCalls(supportsTools) : runtimeMode === "local" ? inferLocalParallelToolCalls(model, extractModelFamily(showRes)) : (
1526
+ // unknown runtime mode — can't even tell if we're cloud or local
1527
+ "unknown"
1528
+ );
1259
1529
  return {
1260
1530
  model,
1261
1531
  reported,
1262
- supportsTools: reportedSet.has("tools"),
1532
+ supportsTools,
1263
1533
  supportsVision: reportedSet.has("vision"),
1264
1534
  supportsEmbedding: reportedSet.has("embedding"),
1265
1535
  supportsCompletion: reportedSet.has("completion") || !reportedSet.has("embedding"),
@@ -1267,7 +1537,8 @@ async function detectModelCapabilities(http, model, signal) {
1267
1537
  ...showRes.thinking !== void 0 ? { thinking: showRes.thinking } : {},
1268
1538
  ...contextLength !== void 0 ? { contextLength } : {},
1269
1539
  supportsStreaming: true,
1270
- supportsStructuredOutputRequest: inferRuntimeMode(http.baseUrl) !== "cloud"
1540
+ supportsStructuredOutputRequest: runtimeMode !== "cloud",
1541
+ parallelToolCalls
1271
1542
  };
1272
1543
  }
1273
1544
  async function listAvailableModels(http) {
@@ -1412,7 +1683,131 @@ function ensureToolCallIds(toolCalls) {
1412
1683
  return toolCalls.map((tc) => tc.id !== void 0 ? tc : { ...tc, id: generateToolCallId() });
1413
1684
  }
1414
1685
 
1686
+ // src/tools/tool-call-accumulator.ts
1687
+ function isSameToolCall(a, b, aIndex, bIndex) {
1688
+ if (a.id !== void 0 && b.id !== void 0) {
1689
+ return a.id === b.id;
1690
+ }
1691
+ if (aIndex !== void 0 && bIndex !== void 0) {
1692
+ return aIndex === bIndex;
1693
+ }
1694
+ return false;
1695
+ }
1696
+ function mergeToolCall(earlier, later) {
1697
+ const earlierFn = earlier.function;
1698
+ const laterFn = later.function;
1699
+ const earlierArgs = earlierFn.arguments;
1700
+ const laterArgs = laterFn.arguments;
1701
+ const mergedFn = {
1702
+ name: laterFn.name || earlierFn.name,
1703
+ arguments: earlierArgs !== void 0 && laterArgs !== void 0 ? { ...earlierArgs, ...laterArgs } : laterArgs ?? earlierArgs ?? {}
1704
+ };
1705
+ return {
1706
+ // id: prefer the later one if present, else the earlier one
1707
+ ...later.id !== void 0 ? { id: later.id } : earlier.id !== void 0 ? { id: earlier.id } : {},
1708
+ function: mergedFn
1709
+ };
1710
+ }
1711
+ function mergeToolCallArgumentsString(earlier, later) {
1712
+ const earlierArgs = earlier.function.arguments;
1713
+ const laterArgs = later.function.arguments;
1714
+ const concatenated = (typeof earlierArgs === "string" ? earlierArgs : "") + (typeof laterArgs === "string" ? laterArgs : "");
1715
+ return {
1716
+ ...later.id !== void 0 ? { id: later.id } : earlier.id !== void 0 ? { id: earlier.id } : {},
1717
+ function: {
1718
+ name: later.function.name || earlier.function.name,
1719
+ arguments: concatenated
1720
+ }
1721
+ };
1722
+ }
1723
+ var ToolCallAccumulator = class {
1724
+ entries = [];
1725
+ /**
1726
+ * Process a chunk's `tool_calls` array. Entries that match an
1727
+ * existing accumulated entry (by id or array position) are merged;
1728
+ * new entries are appended.
1729
+ *
1730
+ * Returns the current accumulated state (a shallow copy — entries
1731
+ * themselves are NOT deep-cloned, so callers that mutate
1732
+ * `function.arguments` on a returned entry will mutate the
1733
+ * accumulator's internal state. Use {@link snapshot} for an
1734
+ * explicitly read-only view, or deep-clone the result if you need
1735
+ * full isolation).
1736
+ */
1737
+ process(toolCalls) {
1738
+ if (!toolCalls || toolCalls.length === 0) {
1739
+ return [...this.entries];
1740
+ }
1741
+ for (let i = 0; i < toolCalls.length; i++) {
1742
+ const incoming = toolCalls[i];
1743
+ if (!incoming) continue;
1744
+ let matchedIndex = -1;
1745
+ for (let j = 0; j < this.entries.length; j++) {
1746
+ const existing = this.entries[j];
1747
+ if (existing && isSameToolCall(existing, incoming, j, i)) {
1748
+ matchedIndex = j;
1749
+ break;
1750
+ }
1751
+ }
1752
+ if (matchedIndex >= 0) {
1753
+ this.entries[matchedIndex] = mergeToolCall(this.entries[matchedIndex], incoming);
1754
+ } else {
1755
+ this.entries.push(incoming);
1756
+ }
1757
+ }
1758
+ return [...this.entries];
1759
+ }
1760
+ /**
1761
+ * Returns the final accumulated tool calls. Identical to calling
1762
+ * `process(undefined)` — included for ergonomics so callers can
1763
+ * express "I'm done, give me the result" without a no-op call.
1764
+ *
1765
+ * The returned array is a shallow copy; entries themselves are
1766
+ * the same object references the accumulator holds internally.
1767
+ */
1768
+ finalize() {
1769
+ return [...this.entries];
1770
+ }
1771
+ /**
1772
+ * Returns the current accumulated state without modifying the
1773
+ * accumulator. Useful for mid-stream inspection.
1774
+ *
1775
+ * The returned array is a shallow copy; mutating it (e.g.
1776
+ * `result.push(...)`) does not affect the accumulator, but
1777
+ * mutating an entry's `function.arguments` object DOES affect
1778
+ * the accumulator's internal state because the entry reference
1779
+ * is shared. Deep-clone the result if you need full isolation.
1780
+ */
1781
+ snapshot() {
1782
+ return [...this.entries];
1783
+ }
1784
+ /**
1785
+ * Returns the number of accumulated tool calls so far.
1786
+ */
1787
+ get length() {
1788
+ return this.entries.length;
1789
+ }
1790
+ };
1791
+ function mergeToolCallArrays(earlier, later) {
1792
+ const acc = new ToolCallAccumulator();
1793
+ acc.process(earlier);
1794
+ acc.process(later);
1795
+ return acc.finalize();
1796
+ }
1797
+
1415
1798
  // src/streaming/stream.ts
1799
+ function extractPartialContent(accumulated) {
1800
+ if (typeof accumulated !== "object" || accumulated === null) return "";
1801
+ const record = accumulated;
1802
+ const message = record.message;
1803
+ if (typeof message === "object" && message !== null) {
1804
+ const content = message.content;
1805
+ if (typeof content === "string") return content;
1806
+ }
1807
+ const response = record.response;
1808
+ if (typeof response === "string") return response;
1809
+ return "";
1810
+ }
1416
1811
  var OllamaStream = class {
1417
1812
  constructor(source, mapChunk, aggregate, initial, signal) {
1418
1813
  this.source = source;
@@ -1433,10 +1828,6 @@ var OllamaStream = class {
1433
1828
  }
1434
1829
  }
1435
1830
  }
1436
- source;
1437
- mapChunk;
1438
- aggregate;
1439
- initial;
1440
1831
  mode = "unconsumed";
1441
1832
  listeners = /* @__PURE__ */ new Map();
1442
1833
  finalResultPromise;
@@ -1495,7 +1886,10 @@ var OllamaStream = class {
1495
1886
  } catch (error) {
1496
1887
  this.removeAbortListener?.();
1497
1888
  this.removeAbortListener = void 0;
1498
- const mapped = mapError(error);
1889
+ const mapped = error instanceof OllamaStreamError && error.partialContent === "" ? new OllamaStreamError(error.message, {
1890
+ partialContent: extractPartialContent(accumulated),
1891
+ cause: error
1892
+ }) : mapError(error);
1499
1893
  this.emit({ type: "error", data: { error: mapped } });
1500
1894
  this.rejectFinal(mapped);
1501
1895
  }
@@ -1522,7 +1916,10 @@ var OllamaStream = class {
1522
1916
  }
1523
1917
  }
1524
1918
  } catch (error) {
1525
- const mapped = mapError(error);
1919
+ const mapped = error instanceof OllamaStreamError && error.partialContent === "" ? new OllamaStreamError(error.message, {
1920
+ partialContent: extractPartialContent(accumulated),
1921
+ cause: error
1922
+ }) : mapError(error);
1526
1923
  this.rejectFinal(mapped);
1527
1924
  yield { type: "error", data: { error: mapped } };
1528
1925
  } finally {
@@ -1544,7 +1941,17 @@ function aggregateChat(accumulated, chunk) {
1544
1941
  role: message.role || accumulated.message.role,
1545
1942
  content: accumulated.message.content + (message.content ?? ""),
1546
1943
  thinking: message.thinking !== void 0 ? (accumulated.message.thinking ?? "") + message.thinking : accumulated.message.thinking,
1547
- tool_calls: message.tool_calls?.length ? [...accumulated.message.tool_calls ?? [], ...message.tool_calls] : accumulated.message.tool_calls
1944
+ // Defensive tool-call merging: instead of blindly appending
1945
+ // every chunk's tool_calls array (which would produce
1946
+ // duplicates if Ollama ever starts streaming them
1947
+ // incrementally), merge entries that refer to the same logical
1948
+ // call (matched by id or array position). For the documented
1949
+ // native behavior (tool_calls arrive complete in one chunk)
1950
+ // this is a no-op — the accumulator appends exactly once.
1951
+ //
1952
+ // See src/tools/tool-call-accumulator.ts for the full merge
1953
+ // semantics and the rationale.
1954
+ tool_calls: message.tool_calls?.length || accumulated.message.tool_calls?.length ? mergeToolCallArrays(accumulated.message.tool_calls, message.tool_calls) : void 0
1548
1955
  },
1549
1956
  model: chunk.model,
1550
1957
  done: chunk.done,
@@ -1753,12 +2160,24 @@ function createTimeoutSignal(timeoutMs, userSignal) {
1753
2160
  return { signal: controller.signal, cancel };
1754
2161
  }
1755
2162
 
2163
+ // src/keep-alive.ts
2164
+ var KEEP_ALIVE_UNLOAD = 0;
2165
+ var KEEP_ALIVE_INDEFINITE = -1;
2166
+ function normalizeKeepAlive(keepAlive) {
2167
+ if (keepAlive === void 0) return void 0;
2168
+ if (keepAlive === "unload") return KEEP_ALIVE_UNLOAD;
2169
+ if (keepAlive === "indefinite") return KEEP_ALIVE_INDEFINITE;
2170
+ return keepAlive;
2171
+ }
2172
+ function isKeepAliveSugar(value) {
2173
+ return value === "unload" || value === "indefinite";
2174
+ }
2175
+
1756
2176
  // src/models-client.ts
1757
2177
  var ModelsClient = class {
1758
2178
  constructor(runner) {
1759
2179
  this.runner = runner;
1760
2180
  }
1761
- runner;
1762
2181
  list() {
1763
2182
  return this.runner((http) => listAvailableModels(http), { singleEndpoint: true });
1764
2183
  }
@@ -1865,6 +2284,76 @@ var ModelsClient = class {
1865
2284
  { singleEndpoint: true }
1866
2285
  );
1867
2286
  }
2287
+ /**
2288
+ * Immediately unloads `model` from VRAM/RAM by issuing an empty
2289
+ * `/api/generate` request with `keep_alive: 0`. The server
2290
+ * finalizes any in-flight generation, releases the model's
2291
+ * weights from GPU memory, and frees the slot for subsequent
2292
+ * pipelines (e.g. swap a vision encoder out before loading an
2293
+ * LLM).
2294
+ *
2295
+ * This is the ergonomic equivalent of:
2296
+ *
2297
+ * ```ts
2298
+ * client.generate({ model, prompt: '', keep_alive: 0 });
2299
+ * ```
2300
+ *
2301
+ * Use {@link pin} for the opposite lifecycle — pinning a model
2302
+ * indefinitely in VRAM for hot-loop inference.
2303
+ *
2304
+ * Resolves once the server acknowledges the unload. The promise
2305
+ * rejects on transport errors, model-not-found (404), or auth
2306
+ * failures — callers handling transient issues can retry via the
2307
+ * standard {@link withRetry} policy.
2308
+ *
2309
+ * See: https://github.com/ollama/ollama/blob/main/docs/faq.md
2310
+ * #how-do-i-keep-a-model-loaded-in-memory-or-make-it-unload-immediately
2311
+ */
2312
+ async unload(model) {
2313
+ await this.runner(
2314
+ (http, signal) => http.request({
2315
+ path: "/api/generate",
2316
+ body: { model, prompt: "", keep_alive: KEEP_ALIVE_UNLOAD, stream: false },
2317
+ signal
2318
+ }),
2319
+ { model, singleEndpoint: true }
2320
+ );
2321
+ }
2322
+ /**
2323
+ * Pre-loads and pins `model` into VRAM indefinitely by issuing an
2324
+ * empty `/api/generate` request with `keep_alive: -1`. The server
2325
+ * loads the model into GPU memory (paying the cold-load latency
2326
+ * once) and keeps it resident until either:
2327
+ *
2328
+ * - an explicit {@link unload} call is made,
2329
+ * - the server process is restarted, or
2330
+ * - the host runs out of VRAM and the server's own eviction
2331
+ * policy kicks in.
2332
+ *
2333
+ * This is the ergonomic equivalent of:
2334
+ *
2335
+ * ```ts
2336
+ * client.generate({ model, prompt: '', keep_alive: -1 });
2337
+ * ```
2338
+ *
2339
+ * Use {@link unload} to release the pinned slot when the hot loop
2340
+ * is done — leaving models pinned indefinitely exhausts VRAM and
2341
+ * starves subsequent pipelines.
2342
+ *
2343
+ * Resolves once the server acknowledges the pin (i.e. the model
2344
+ * is fully loaded). The promise rejects on transport errors,
2345
+ * model-not-found (404), or VRAM-exhaustion failures.
2346
+ */
2347
+ async pin(model) {
2348
+ await this.runner(
2349
+ (http, signal) => http.request({
2350
+ path: "/api/generate",
2351
+ body: { model, prompt: "", keep_alive: KEEP_ALIVE_INDEFINITE, stream: false },
2352
+ signal
2353
+ }),
2354
+ { model, singleEndpoint: true }
2355
+ );
2356
+ }
1868
2357
  async createBlob(digest, data) {
1869
2358
  await this.runner(
1870
2359
  (http, signal) => http.request({
@@ -1887,9 +2376,104 @@ var ModelsClient = class {
1887
2376
  { singleEndpoint: true }
1888
2377
  );
1889
2378
  return true;
1890
- } catch {
1891
- return false;
2379
+ } catch (err) {
2380
+ if (err instanceof OllamaNotFoundError) return false;
2381
+ throw err;
2382
+ }
2383
+ }
2384
+ /**
2385
+ * Computes a blob digest the way Ollama's content-addressed store expects:
2386
+ * SHA-256 over the raw bytes, formatted as `sha256:<64 lowercase hex>`.
2387
+ *
2388
+ * Uses the Web Crypto API (`crypto.subtle.digest`), which exists in every
2389
+ * supported runtime — Node.js ≥ 19 exposes it as a global (this SDK
2390
+ * requires Node ≥ 20), and browsers/edge runtimes have always had it.
2391
+ */
2392
+ async computeBlobDigest(data) {
2393
+ const subtle = globalThis.crypto?.subtle;
2394
+ if (subtle === void 0) {
2395
+ throw new OllamaClientError(
2396
+ "computeBlobDigest requires the Web Crypto API (crypto.subtle), unavailable in this runtime.",
2397
+ { code: "crypto_unavailable" }
2398
+ );
2399
+ }
2400
+ const digestBytes = await subtle.digest("SHA-256", data);
2401
+ const hex = Array.from(
2402
+ new Uint8Array(digestBytes),
2403
+ (byte) => byte.toString(16).padStart(2, "0")
2404
+ ).join("");
2405
+ return `sha256:${hex}`;
2406
+ }
2407
+ /**
2408
+ * Computes the SHA-256 digest of `data` and pushes it as a blob in one
2409
+ * step — the content-addressed upload Ollama's own tooling performs
2410
+ * (`curl -T file http://…/api/blobs/sha256:…`). Returns the digest, which
2411
+ * `/api/create`'s `files` field references.
2412
+ *
2413
+ * Pass `digest` to skip recomputation when the caller already knows it —
2414
+ * the server verifies the content against the digest and rejects mismatches.
2415
+ */
2416
+ async createBlobFromData(data, opts) {
2417
+ const digest = opts?.digest ?? await this.computeBlobDigest(data);
2418
+ const alreadyExisted = await this.checkBlob(digest);
2419
+ if (!alreadyExisted) {
2420
+ await this.createBlob(digest, data);
2421
+ }
2422
+ return { digest, alreadyExisted };
2423
+ }
2424
+ /**
2425
+ * Reads a local file and uploads it as a blob (Node.js only — dynamic
2426
+ * `node:fs` import, so browser bundles never pull it in). The file name
2427
+ * (basename) is what `/api/create`'s `files` map expects as the key, so
2428
+ * this returns it alongside the digest.
2429
+ */
2430
+ async createBlobFromFile(path, opts) {
2431
+ const nodeFsModule = "node:fs";
2432
+ const { promises: fs } = await import(nodeFsModule);
2433
+ const data = await fs.readFile(path);
2434
+ const result = await this.createBlobFromData(new Uint8Array(data), {
2435
+ ...opts
2436
+ });
2437
+ const fileName = path.split(/[\\/]/).pop() ?? path;
2438
+ return { ...result, fileName };
2439
+ }
2440
+ /**
2441
+ * One-shot custom-model publishing from raw GGUF weights, following the
2442
+ * protocol Ollama's API reference documents: push a blob for the GGUF
2443
+ * bytes, then `POST /api/create` with `files: { <fileName>: <digest> }`.
2444
+ * Split GGUFs are supported via `files` — pass every shard's path.
2445
+ *
2446
+ * ```ts
2447
+ * await client.models.createModelFromGguf('my-model', '/path/to/model.gguf', {
2448
+ * template: '{{ .Prompt }}',
2449
+ * });
2450
+ * ```
2451
+ *
2452
+ * @param model Name for the new model (e.g. `'my-model:latest'`).
2453
+ * @param gguf Path(s) to GGUF file(s) on the local filesystem.
2454
+ * @param opts Everything else `/api/create` accepts (`template`, `system`,
2455
+ * `parameters`, `license`, `quantize`, …) except `files` and `from`.
2456
+ */
2457
+ async createModelFromGguf(model, gguf, opts) {
2458
+ const paths = typeof gguf === "string" ? [gguf] : gguf;
2459
+ if (paths.length === 0) {
2460
+ throw new OllamaClientError("createModelFromGguf: at least one GGUF path is required.", {
2461
+ code: "invalid_request"
2462
+ });
1892
2463
  }
2464
+ const files = {};
2465
+ for (const path of paths) {
2466
+ const uploaded = await this.createBlobFromFile(path, { signal: opts?.signal });
2467
+ files[uploaded.fileName] = uploaded.digest;
2468
+ }
2469
+ const {
2470
+ model: _model,
2471
+ files: _files,
2472
+ from: _from,
2473
+ stream: _stream,
2474
+ ...rest
2475
+ } = opts ?? {};
2476
+ return this.create({ ...rest, model, files, stream: false });
1893
2477
  }
1894
2478
  };
1895
2479
 
@@ -1917,7 +2501,6 @@ var OpenAIChatCompletionStream = class {
1917
2501
  }
1918
2502
  }
1919
2503
  }
1920
- source;
1921
2504
  finalResultPromise;
1922
2505
  resolveFinal;
1923
2506
  rejectFinal;
@@ -2061,7 +2644,6 @@ var OpenAICompletionStream = class {
2061
2644
  }
2062
2645
  }
2063
2646
  }
2064
- source;
2065
2647
  finalResultPromise;
2066
2648
  resolveFinal;
2067
2649
  rejectFinal;
@@ -2142,7 +2724,6 @@ var OpenAIResponsesStream = class {
2142
2724
  }
2143
2725
  }
2144
2726
  }
2145
- source;
2146
2727
  finalResultPromise;
2147
2728
  resolveFinal;
2148
2729
  rejectFinal;
@@ -2367,8 +2948,6 @@ var OpenAICompatClient = class {
2367
2948
  this.http = http;
2368
2949
  this.runner = runner;
2369
2950
  }
2370
- http;
2371
- runner;
2372
2951
  request(operation, model, signal, holdUntil) {
2373
2952
  if (this.runner) {
2374
2953
  return this.runner(
@@ -2588,7 +3167,6 @@ var AnthropicMessagesStream = class {
2588
3167
  }
2589
3168
  }
2590
3169
  }
2591
- source;
2592
3170
  finalResultPromise;
2593
3171
  resolveFinal;
2594
3172
  rejectFinal;
@@ -2703,8 +3281,6 @@ var AnthropicCompatClient = class {
2703
3281
  this.http = http;
2704
3282
  this.runner = runner;
2705
3283
  }
2706
- http;
2707
- runner;
2708
3284
  request(operation, model, signal, holdUntil) {
2709
3285
  if (this.runner) {
2710
3286
  return this.runner(
@@ -2750,6 +3326,32 @@ var AnthropicCompatClient = class {
2750
3326
  return this.createMessage(request, signal);
2751
3327
  }
2752
3328
  };
3329
+ var IncludedBalanceSchema = zod.z.object({
3330
+ balance_usd: zod.z.number(),
3331
+ allowance_usd: zod.z.number(),
3332
+ period: zod.z.object({
3333
+ from: zod.z.string(),
3334
+ until: zod.z.string()
3335
+ })
3336
+ });
3337
+ var LegacyBalanceLimitSchema = zod.z.object({
3338
+ remaining_percent: zod.z.number().min(0).max(100),
3339
+ resets_at: zod.z.string()
3340
+ });
3341
+
3342
+ // src/generated/models/LegacyIncludedBalance.schema.ts
3343
+ var LegacyIncludedBalanceSchema = zod.z.object({
3344
+ session: LegacyBalanceLimitSchema,
3345
+ weekly: LegacyBalanceLimitSchema
3346
+ });
3347
+
3348
+ // src/generated/models/BalanceResponse.schema.ts
3349
+ var BalanceResponseSchema = zod.z.object({
3350
+ included: zod.z.union([IncludedBalanceSchema, LegacyIncludedBalanceSchema]),
3351
+ purchased: zod.z.object({
3352
+ balance_usd: zod.z.number()
3353
+ })
3354
+ });
2753
3355
  var ToolCallSchema = zod.z.object({
2754
3356
  function: zod.z.object({
2755
3357
  name: zod.z.string(),
@@ -2798,28 +3400,45 @@ var ChatRequestSchema = zod.z.object({
2798
3400
  logprobs: zod.z.boolean().optional(),
2799
3401
  top_logprobs: zod.z.number().optional()
2800
3402
  });
2801
- var GenerateRequestSchema = zod.z.object({
2802
- model: zod.z.string(),
2803
- prompt: zod.z.string().optional(),
2804
- suffix: zod.z.string().optional(),
2805
- images: zod.z.array(zod.z.string()).optional(),
2806
- format: zod.z.union([zod.z.string(), zod.z.object({})]).optional(),
2807
- system: zod.z.string().optional(),
2808
- stream: zod.z.boolean().optional(),
2809
- think: ThinkValueSchema.optional(),
2810
- raw: zod.z.boolean().optional(),
2811
- keep_alive: zod.z.union([zod.z.string(), zod.z.number()]).optional(),
2812
- options: ModelOptionsSchema.optional(),
2813
- logprobs: zod.z.boolean().optional(),
2814
- top_logprobs: zod.z.number().optional()
3403
+ var TokenLogprobSchema = zod.z.object({
3404
+ token: zod.z.string().optional(),
3405
+ logprob: zod.z.number().optional(),
3406
+ bytes: zod.z.array(zod.z.number()).optional()
2815
3407
  });
2816
- var EmbedRequestSchema = zod.z.object({
2817
- model: zod.z.string(),
2818
- input: zod.z.union([zod.z.string(), zod.z.array(zod.z.string())]),
2819
- truncate: zod.z.boolean().optional(),
2820
- dimensions: zod.z.number().optional(),
2821
- keep_alive: zod.z.string().optional(),
2822
- options: ModelOptionsSchema.optional()
3408
+
3409
+ // src/generated/models/Logprob.schema.ts
3410
+ var LogprobSchema = zod.z.object({
3411
+ token: zod.z.string().optional(),
3412
+ logprob: zod.z.number().optional(),
3413
+ bytes: zod.z.array(zod.z.number()).optional(),
3414
+ top_logprobs: zod.z.array(TokenLogprobSchema).optional()
3415
+ });
3416
+
3417
+ // src/generated/models/ChatResponse.schema.ts
3418
+ var ChatResponseSchema = zod.z.object({
3419
+ model: zod.z.string().optional(),
3420
+ created_at: zod.z.string().optional(),
3421
+ message: zod.z.object({
3422
+ role: zod.z.enum(["assistant"]).optional(),
3423
+ content: zod.z.string().optional(),
3424
+ thinking: zod.z.string().optional(),
3425
+ tool_calls: zod.z.array(ToolCallSchema).optional(),
3426
+ images: zod.z.array(zod.z.string()).optional()
3427
+ }).optional(),
3428
+ done: zod.z.boolean().optional(),
3429
+ done_reason: zod.z.string().optional(),
3430
+ total_duration: zod.z.number().optional(),
3431
+ load_duration: zod.z.number().optional(),
3432
+ prompt_eval_count: zod.z.number().optional(),
3433
+ prompt_eval_cached_count: zod.z.number().optional(),
3434
+ prompt_eval_duration: zod.z.number().optional(),
3435
+ eval_count: zod.z.number().optional(),
3436
+ eval_duration: zod.z.number().optional(),
3437
+ logprobs: zod.z.array(LogprobSchema).optional()
3438
+ });
3439
+ var CopyRequestSchema = zod.z.object({
3440
+ source: zod.z.string(),
3441
+ destination: zod.z.string()
2823
3442
  });
2824
3443
  var CreateRequestSchema = zod.z.object({
2825
3444
  model: zod.z.string(),
@@ -2836,50 +3455,296 @@ var CreateRequestSchema = zod.z.object({
2836
3455
  quantize: zod.z.string().optional(),
2837
3456
  draft_quantize: zod.z.string().optional(),
2838
3457
  requires: zod.z.string().optional(),
3458
+ capabilities: zod.z.array(zod.z.string()).optional(),
2839
3459
  stream: zod.z.boolean().optional()
2840
3460
  });
2841
- var CopyRequestSchema = zod.z.object({
2842
- source: zod.z.string(),
2843
- destination: zod.z.string()
2844
- });
2845
3461
  var DeleteRequestSchema = zod.z.object({
2846
3462
  model: zod.z.string()
2847
3463
  });
2848
- var PullRequestSchema = zod.z.object({
3464
+ var EmbedRequestSchema = zod.z.object({
2849
3465
  model: zod.z.string(),
2850
- insecure: zod.z.boolean().optional(),
2851
- stream: zod.z.boolean().optional()
3466
+ input: zod.z.union([zod.z.string(), zod.z.array(zod.z.string())]),
3467
+ truncate: zod.z.boolean().optional(),
3468
+ dimensions: zod.z.number().optional(),
3469
+ keep_alive: zod.z.string().optional(),
3470
+ options: ModelOptionsSchema.optional()
2852
3471
  });
2853
- var PushRequestSchema = zod.z.object({
2854
- model: zod.z.string(),
2855
- insecure: zod.z.boolean().optional(),
2856
- stream: zod.z.boolean().optional()
3472
+ var EmbedResponseSchema = zod.z.object({
3473
+ model: zod.z.string().optional(),
3474
+ embeddings: zod.z.array(zod.z.array(zod.z.number())).optional(),
3475
+ total_duration: zod.z.number().optional(),
3476
+ load_duration: zod.z.number().optional(),
3477
+ prompt_eval_count: zod.z.number().optional()
2857
3478
  });
2858
- var ShowRequestSchema = zod.z.object({
3479
+ var GenerateRequestSchema = zod.z.object({
2859
3480
  model: zod.z.string(),
2860
- verbose: zod.z.boolean().optional()
3481
+ prompt: zod.z.string().optional(),
3482
+ suffix: zod.z.string().optional(),
3483
+ images: zod.z.array(zod.z.string()).optional(),
3484
+ format: zod.z.union([zod.z.string(), zod.z.object({})]).optional(),
3485
+ system: zod.z.string().optional(),
3486
+ stream: zod.z.boolean().optional(),
3487
+ think: ThinkValueSchema.optional(),
3488
+ raw: zod.z.boolean().optional(),
3489
+ keep_alive: zod.z.union([zod.z.string(), zod.z.number()]).optional(),
3490
+ options: ModelOptionsSchema.optional(),
3491
+ logprobs: zod.z.boolean().optional(),
3492
+ top_logprobs: zod.z.number().optional()
2861
3493
  });
2862
-
3494
+ var GenerateResponseSchema = zod.z.object({
3495
+ model: zod.z.string().optional(),
3496
+ created_at: zod.z.string().optional(),
3497
+ response: zod.z.string().optional(),
3498
+ thinking: zod.z.string().optional(),
3499
+ done: zod.z.boolean().optional(),
3500
+ done_reason: zod.z.string().optional(),
3501
+ total_duration: zod.z.number().optional(),
3502
+ load_duration: zod.z.number().optional(),
3503
+ prompt_eval_count: zod.z.number().optional(),
3504
+ prompt_eval_cached_count: zod.z.number().optional(),
3505
+ prompt_eval_duration: zod.z.number().optional(),
3506
+ eval_count: zod.z.number().optional(),
3507
+ eval_duration: zod.z.number().optional(),
3508
+ logprobs: zod.z.array(LogprobSchema).optional()
3509
+ });
3510
+ var ModelSummarySchema = zod.z.object({
3511
+ name: zod.z.string().optional(),
3512
+ model: zod.z.string().optional(),
3513
+ remote_model: zod.z.string().optional(),
3514
+ remote_host: zod.z.string().optional(),
3515
+ modified_at: zod.z.string().optional(),
3516
+ size: zod.z.number().optional(),
3517
+ digest: zod.z.string().optional(),
3518
+ details: zod.z.object({
3519
+ format: zod.z.string().optional(),
3520
+ family: zod.z.string().optional(),
3521
+ families: zod.z.union([zod.z.array(zod.z.string()), zod.z.null()]).optional(),
3522
+ parameter_size: zod.z.string().optional(),
3523
+ quantization_level: zod.z.string().optional()
3524
+ }).optional()
3525
+ });
3526
+
3527
+ // src/generated/models/ListResponse.schema.ts
3528
+ var ListResponseSchema = zod.z.object({
3529
+ models: zod.z.array(ModelSummarySchema).optional()
3530
+ });
3531
+ var PsSchema = zod.z.object({
3532
+ name: zod.z.string().optional(),
3533
+ model: zod.z.string().optional(),
3534
+ size: zod.z.number().optional(),
3535
+ digest: zod.z.string().optional(),
3536
+ details: zod.z.object({}).optional(),
3537
+ expires_at: zod.z.string().optional(),
3538
+ size_vram: zod.z.number().optional(),
3539
+ context_length: zod.z.number().optional()
3540
+ });
3541
+
3542
+ // src/generated/models/PsResponse.schema.ts
3543
+ var PsResponseSchema = zod.z.object({
3544
+ models: zod.z.array(PsSchema).optional()
3545
+ });
3546
+ var PullRequestSchema = zod.z.object({
3547
+ model: zod.z.string(),
3548
+ insecure: zod.z.boolean().optional(),
3549
+ stream: zod.z.boolean().optional()
3550
+ });
3551
+ var PushRequestSchema = zod.z.object({
3552
+ model: zod.z.string(),
3553
+ insecure: zod.z.boolean().optional(),
3554
+ stream: zod.z.boolean().optional()
3555
+ });
3556
+ var ShowRequestSchema = zod.z.object({
3557
+ model: zod.z.string(),
3558
+ verbose: zod.z.boolean().optional()
3559
+ });
3560
+ var ThinkingSchema = zod.z.object({
3561
+ values: zod.z.array(zod.z.union([zod.z.boolean(), zod.z.string()])),
3562
+ default: zod.z.union([zod.z.boolean(), zod.z.string()])
3563
+ });
3564
+
3565
+ // src/generated/models/ShowResponse.schema.ts
3566
+ var ShowResponseSchema = zod.z.object({
3567
+ thinking: ThinkingSchema.optional(),
3568
+ parameters: zod.z.string().optional(),
3569
+ license: zod.z.string().optional(),
3570
+ modified_at: zod.z.string().optional(),
3571
+ details: zod.z.object({}).optional(),
3572
+ template: zod.z.string().optional(),
3573
+ capabilities: zod.z.array(zod.z.string()).optional(),
3574
+ model_info: zod.z.object({}).optional()
3575
+ });
3576
+ var StatusResponseSchema = zod.z.object({
3577
+ status: zod.z.string().optional()
3578
+ });
3579
+ var SystemOneContentSchema = zod.z.union([
3580
+ zod.z.string().regex(new RegExp("\\S")),
3581
+ zod.z.object({}).catchall(zod.z.unknown()),
3582
+ zod.z.array(zod.z.unknown())
3583
+ ]);
3584
+
3585
+ // src/generated/models/SystemOneChoiceQuestion.schema.ts
3586
+ var SystemOneChoiceQuestionSchema = zod.z.object({
3587
+ type: zod.z.enum(["choice"]),
3588
+ instructions: SystemOneContentSchema,
3589
+ criteria: zod.z.object({}).catchall(zod.z.union([zod.z.string(), zod.z.null()])).refine((v) => Object.keys(v).length >= 2, { message: "must have at least 2 properties" }).refine((v) => Object.keys(v).length <= 26, { message: "must have at most 26 properties" }).refine((v) => Object.keys(v).every((k) => new RegExp("\\S").test(k)), {
3590
+ message: 'property names must match pattern "\\S"'
3591
+ })
3592
+ });
3593
+ var SystemOneNoulQuestionSchema = zod.z.object({
3594
+ type: zod.z.enum(["noul"]),
3595
+ instructions: SystemOneContentSchema,
3596
+ criteria: zod.z.object({
3597
+ false: zod.z.string().optional(),
3598
+ true: zod.z.string().optional()
3599
+ }).optional()
3600
+ });
3601
+ var SystemOneScoreQuestionSchema = zod.z.object({
3602
+ type: zod.z.enum(["score"]),
3603
+ instructions: SystemOneContentSchema,
3604
+ criteria: zod.z.array(zod.z.string()).min(2).max(26)
3605
+ });
3606
+
3607
+ // src/generated/models/SystemOneRequest.schema.ts
3608
+ var SystemOneRequestSchema = zod.z.object({
3609
+ model: zod.z.string().regex(new RegExp("\\S")),
3610
+ state: SystemOneContentSchema,
3611
+ images: zod.z.array(zod.z.string()).optional(),
3612
+ questions: zod.z.object({}).catchall(
3613
+ zod.z.union([
3614
+ SystemOneChoiceQuestionSchema,
3615
+ SystemOneNoulQuestionSchema,
3616
+ SystemOneScoreQuestionSchema
3617
+ ])
3618
+ ).refine((v) => Object.keys(v).length >= 1, { message: "must have at least 1 properties" }).refine((v) => Object.keys(v).length <= 64, { message: "must have at most 64 properties" }).refine((v) => Object.keys(v).every((k) => new RegExp("\\S").test(k)), {
3619
+ message: 'property names must match pattern "\\S"'
3620
+ }),
3621
+ keep_alive: zod.z.union([zod.z.string(), zod.z.number()]).optional()
3622
+ });
3623
+ var SystemOneConfidenceSchema = zod.z.number().min(0).max(1);
3624
+ var SystemOneProbabilitiesSchema = zod.z.object({}).catchall(zod.z.number().min(0).max(1));
3625
+
3626
+ // src/generated/models/SystemOneChoiceAnswer.schema.ts
3627
+ var SystemOneChoiceAnswerSchema = zod.z.object({
3628
+ type: zod.z.enum(["choice"]),
3629
+ choice: zod.z.string(),
3630
+ probabilities: SystemOneProbabilitiesSchema,
3631
+ confidence: SystemOneConfidenceSchema
3632
+ });
3633
+ var SystemOneNoulAnswerSchema = zod.z.object({
3634
+ type: zod.z.enum(["noul"]),
3635
+ noul: zod.z.number().min(0).max(1)
3636
+ });
3637
+ var SystemOneScoreAnswerSchema = zod.z.object({
3638
+ type: zod.z.enum(["score"]),
3639
+ score: zod.z.number().min(0).max(25),
3640
+ legend: zod.z.object({}).catchall(zod.z.string()),
3641
+ probabilities: SystemOneProbabilitiesSchema,
3642
+ confidence: SystemOneConfidenceSchema
3643
+ });
3644
+
3645
+ // src/generated/models/SystemOneResponse.schema.ts
3646
+ var SystemOneResponseSchema = zod.z.object({
3647
+ model: zod.z.string(),
3648
+ answers: zod.z.object({}).catchall(
3649
+ zod.z.union([SystemOneChoiceAnswerSchema, SystemOneNoulAnswerSchema, SystemOneScoreAnswerSchema])
3650
+ ),
3651
+ usage: zod.z.object({
3652
+ input_tokens: zod.z.number().min(0),
3653
+ output_tokens: zod.z.number().min(0)
3654
+ })
3655
+ });
3656
+ var UsageMetricsSchema = zod.z.object({
3657
+ request_count: zod.z.number().min(0),
3658
+ usage_usd: zod.z.number().optional(),
3659
+ input_tokens: zod.z.number().optional(),
3660
+ cached_input_tokens: zod.z.number().optional(),
3661
+ output_tokens: zod.z.number().optional()
3662
+ });
3663
+
3664
+ // src/generated/models/UsageBucket.schema.ts
3665
+ var UsageBucketSchema = zod.z.intersection(
3666
+ UsageMetricsSchema,
3667
+ zod.z.object({
3668
+ from: zod.z.string(),
3669
+ until: zod.z.string(),
3670
+ partial: zod.z.boolean().optional()
3671
+ })
3672
+ );
3673
+
3674
+ // src/generated/models/UsageResponse.schema.ts
3675
+ var UsageResponseSchema = zod.z.object({
3676
+ range: zod.z.enum(["24h", "7d", "30d"]),
3677
+ scope: zod.z.enum(["self", "team"]),
3678
+ granularity: zod.z.enum(["hour", "day"]),
3679
+ from: zod.z.string(),
3680
+ until: zod.z.string(),
3681
+ totals: UsageMetricsSchema,
3682
+ buckets: zod.z.array(UsageBucketSchema)
3683
+ });
3684
+ var VersionResponseSchema = zod.z.object({
3685
+ version: zod.z.string().optional()
3686
+ });
3687
+ var WebFetchRequestSchema = zod.z.object({
3688
+ url: zod.z.string()
3689
+ });
3690
+ var WebFetchResponseSchema = zod.z.object({
3691
+ title: zod.z.string().optional(),
3692
+ content: zod.z.string().optional(),
3693
+ links: zod.z.array(zod.z.string()).optional()
3694
+ });
3695
+ var WebSearchRequestSchema = zod.z.object({
3696
+ query: zod.z.string(),
3697
+ max_results: zod.z.number().min(1).max(10).optional()
3698
+ });
3699
+ var WebSearchResultSchema = zod.z.object({
3700
+ title: zod.z.string().optional(),
3701
+ url: zod.z.string().optional(),
3702
+ content: zod.z.string().optional()
3703
+ });
3704
+
3705
+ // src/generated/models/WebSearchResponse.schema.ts
3706
+ var WebSearchResponseSchema = zod.z.object({
3707
+ results: zod.z.array(WebSearchResultSchema).optional()
3708
+ });
3709
+
2863
3710
  // src/generated/runtime/schema-registry.ts
2864
3711
  var requestSchemas = {
2865
3712
  chat: ChatRequestSchema,
2866
- generate: GenerateRequestSchema,
2867
- embed: EmbedRequestSchema,
2868
- create: CreateRequestSchema,
2869
3713
  copy: CopyRequestSchema,
3714
+ create: CreateRequestSchema,
2870
3715
  delete: DeleteRequestSchema,
3716
+ embed: EmbedRequestSchema,
3717
+ generate: GenerateRequestSchema,
2871
3718
  pull: PullRequestSchema,
2872
3719
  push: PushRequestSchema,
2873
- show: ShowRequestSchema
2874
- // Operations without a registered request schema (blobs, ps, tags,
2875
- // version, systemOne, openai*, anthropic*) skip validation. This is
2876
- // deliberate — the OpenAPI spec doesn't define request bodies for
2877
- // GET/HEAD operations, and the compat-surface request schemas (OpenAI,
2878
- // Anthropic) are richer than what the IR currently models.
3720
+ show: ShowRequestSchema,
3721
+ systemOne: SystemOneRequestSchema,
3722
+ webFetch: WebFetchRequestSchema,
3723
+ webSearch: WebSearchRequestSchema
3724
+ };
3725
+ var responseSchemas = {
3726
+ balance: BalanceResponseSchema,
3727
+ chat: ChatResponseSchema,
3728
+ create: StatusResponseSchema,
3729
+ embed: EmbedResponseSchema,
3730
+ generate: GenerateResponseSchema,
3731
+ ps: PsResponseSchema,
3732
+ pull: StatusResponseSchema,
3733
+ push: StatusResponseSchema,
3734
+ show: ShowResponseSchema,
3735
+ systemOne: SystemOneResponseSchema,
3736
+ tags: ListResponseSchema,
3737
+ usage: UsageResponseSchema,
3738
+ version: VersionResponseSchema,
3739
+ webFetch: WebFetchResponseSchema,
3740
+ webSearch: WebSearchResponseSchema
2879
3741
  };
2880
3742
  function getRequestSchema(operationId) {
2881
3743
  return requestSchemas[operationId];
2882
3744
  }
3745
+ function getResponseSchema(operationId) {
3746
+ return responseSchemas[operationId];
3747
+ }
2883
3748
 
2884
3749
  // src/generated/runtime/runtime.ts
2885
3750
  function compareVersions(a, b) {
@@ -2892,18 +3757,13 @@ function compareVersions(a, b) {
2892
3757
  }
2893
3758
  return 0;
2894
3759
  }
2895
- function assertOperationAllowed(operation, options) {
2896
- const localMode = options.localMode ?? true;
2897
- if (!localMode && !operation.environment.cloud) {
2898
- throw new OllamaGenericClientError(
2899
- `Operation ${operation.operationId} (${operation.method} ${operation.path}) is local-only and not supported in cloud mode.`
2900
- );
2901
- }
2902
- if (operation.constraints?.minOllamaVersion && options.serverVersion && compareVersions(options.serverVersion, operation.constraints.minOllamaVersion) < 0) {
2903
- throw new OllamaGenericClientError(
2904
- `Operation ${operation.operationId} (${operation.method} ${operation.path}) requires Ollama >= ${operation.constraints.minOllamaVersion} (server reports ${options.serverVersion}).`
2905
- );
2906
- }
3760
+ async function fetchServerVersion(http) {
3761
+ const result = await http.request({
3762
+ path: "/api/version",
3763
+ method: "GET"
3764
+ });
3765
+ if (result && typeof result.version === "string") return result.version;
3766
+ return void 0;
2907
3767
  }
2908
3768
  function shouldStream(req) {
2909
3769
  if (req.body && typeof req.body === "object" && "stream" in req.body) {
@@ -2925,7 +3785,35 @@ var OllamaRuntime = class {
2925
3785
  constructor(options) {
2926
3786
  this.options = options;
2927
3787
  }
2928
- options;
3788
+ /**
3789
+ * Cached promise of the Ollama server version, populated lazily the first
3790
+ * time a version-gated operation is invoked. `undefined` until then.
3791
+ *
3792
+ * Wave 12 (P0 #5): see {@link OllamaRuntimeOptions.enforceVersion}.
3793
+ */
3794
+ serverVersionCache;
3795
+ /**
3796
+ * Wave 15 (P0): resolve the HTTP backend for a given operation.
3797
+ *
3798
+ * Operations with a declared `host` (e.g. web search/fetch at
3799
+ * https://ollama.com) must route to a HttpClient pointed at that host,
3800
+ * not the default one. The runtime accepts an optional `cloudHttp`
3801
+ * backend for this purpose — if the operation declares a host and no
3802
+ * cloud backend is configured, the runtime throws rather than silently
3803
+ * sending the request to the wrong server.
3804
+ */
3805
+ resolveHttpBackend(operation) {
3806
+ if (operation.host) {
3807
+ const cloudHttp = this.options.cloudHttp;
3808
+ if (!cloudHttp) {
3809
+ throw new OllamaGenericClientError(
3810
+ `Operation ${operation.operationId} (${operation.method} ${operation.path}) targets host "${operation.host}" but the runtime has no cloudHttp backend configured. Use OllamaClient.webSearch() / OllamaClient.webFetch() for cloud-hosted operations, or construct OllamaRuntime with a cloudHttp option.`
3811
+ );
3812
+ }
3813
+ return cloudHttp;
3814
+ }
3815
+ return this.options.http;
3816
+ }
2929
3817
  /**
2930
3818
  * Invoke an operation. Called by every generated API method.
2931
3819
  *
@@ -2934,7 +3822,7 @@ var OllamaRuntime = class {
2934
3822
  * class's overloads pick the right return type at compile time.
2935
3823
  */
2936
3824
  async invoke(req) {
2937
- assertOperationAllowed(req.operation, this.options);
3825
+ await this.assertOperationAllowed(req.operation);
2938
3826
  let body = buildBody(req);
2939
3827
  if (this.options.validateRequests === true && body !== void 0) {
2940
3828
  const schema = getRequestSchema(req.operation.operationId);
@@ -2953,32 +3841,1133 @@ var OllamaRuntime = class {
2953
3841
  body = result.data;
2954
3842
  }
2955
3843
  }
3844
+ if (body !== void 0) {
3845
+ const serialized = serializeForByteCount(body);
3846
+ const hasImages = bodyHasImages(body);
3847
+ const maxBytesWithImages = req.operation.constraints?.maxRequestBytesWithImages;
3848
+ const maxBytesBase = req.operation.constraints?.maxRequestBytes;
3849
+ const applicableLimit = hasImages && maxBytesWithImages !== void 0 ? maxBytesWithImages : maxBytesBase;
3850
+ if (applicableLimit !== void 0 && serialized.byteLength > applicableLimit) {
3851
+ throw new OllamaRequestTooLargeError(
3852
+ `Operation ${req.operation.operationId} (${req.operation.method} ${req.operation.path}) request body is ${serialized.byteLength} bytes, exceeding the contract limit of ${applicableLimit} bytes` + (hasImages ? " (images limit)." : "."),
3853
+ {
3854
+ operationId: req.operation.operationId,
3855
+ actualBytes: serialized.byteLength,
3856
+ maxBytes: applicableLimit,
3857
+ request: { method: req.operation.method, url: req.operation.path }
3858
+ }
3859
+ );
3860
+ }
3861
+ }
2956
3862
  const streaming = shouldStream(req);
3863
+ const resolvedPath = resolvePathParams(req.operation.path, req.pathParams);
3864
+ const pathWithQuery = applyQueryParams(resolvedPath, req.queryParams);
3865
+ const model = req.model ?? extractModelFromBody(body);
3866
+ const headers = {};
3867
+ if (req.headerParams) {
3868
+ for (const [key, value] of Object.entries(req.headerParams)) {
3869
+ headers[key] = value;
3870
+ }
3871
+ }
2957
3872
  const httpReq = {
2958
- path: req.operation.path,
3873
+ path: pathWithQuery,
2959
3874
  method: req.operation.method,
2960
3875
  ...body !== void 0 ? { body } : {},
3876
+ ...Object.keys(headers).length > 0 ? { headers } : {},
2961
3877
  ...req.signal !== void 0 ? { signal: req.signal } : {},
2962
- // For streaming calls, request the raw Response so we can pipe it
2963
- // through parseNdjsonStream directly. This inherits middleware,
2964
- // retry, telemetry, and error-mapping from HttpClient — the
2965
- // previous Wave 3 implementation bypassed them with a direct
2966
- // `fetch()` call.
2967
- ...streaming ? { raw: true } : {}
3878
+ ...streaming ? { raw: true } : {},
3879
+ ...model !== void 0 ? { model } : {}
2968
3880
  };
2969
3881
  if (!streaming) {
2970
- return await this.options.http.request(httpReq);
3882
+ const result = await this.resolveHttpBackend(req.operation).request(httpReq);
3883
+ if (this.options.validateResponses === true) {
3884
+ const responseSchema = getResponseSchema(req.operation.operationId);
3885
+ if (responseSchema) {
3886
+ const result2 = responseSchema.safeParse(result);
3887
+ if (!result2.success) {
3888
+ throw new OllamaResponseValidationError(
3889
+ `Response validation failed for operation "${req.operation.operationId}" (${req.operation.method} ${req.operation.path}): ` + result2.error.issues.map((i) => `${i.path.join(".") || "<root>"}: ${i.message}`).join("; "),
3890
+ {
3891
+ operationId: req.operation.operationId,
3892
+ issues: result2.error.issues,
3893
+ request: { method: req.operation.method, url: req.operation.path }
3894
+ }
3895
+ );
3896
+ }
3897
+ }
3898
+ }
3899
+ return result;
2971
3900
  }
2972
- const response = await this.options.http.request(httpReq);
3901
+ const response = await this.resolveHttpBackend(req.operation).request(httpReq);
2973
3902
  if (!response.body) {
2974
3903
  throw new OllamaGenericClientError(
2975
3904
  `Operation ${req.operation.operationId}: streaming response had no body.`
2976
3905
  );
2977
3906
  }
2978
- return parseNdjsonStream(response.body);
3907
+ return parseStreamByMode(req.operation.transport.mode, response.body);
3908
+ }
3909
+ /**
3910
+ * Apply environment + version guards before delegating to HttpClient.
3911
+ *
3912
+ * Throws {@link OllamaGenericClientError} when:
3913
+ * - The operation is local-only and the runtime is in cloud mode.
3914
+ * - The operation has `constraints.minOllamaVersion` and the server is older.
3915
+ *
3916
+ * Wave 12 (P0 #5): when `minOllamaVersion` is declared but the runtime's
3917
+ * `serverVersion` is unknown AND `enforceVersion` is `'auto'` or
3918
+ * `'strict'`, the runtime lazily fetches `/api/version` (cached on the
3919
+ * runtime instance) so the constraint is actually enforced even when the
3920
+ * caller didn't supply it. Previously the constraint was only enforced
3921
+ * when `serverVersion` was manually supplied — meaning in practice it was
3922
+ * almost never enforced.
3923
+ */
3924
+ async assertOperationAllowed(operation) {
3925
+ const localMode = this.options.localMode ?? true;
3926
+ if (!localMode && !operation.environment.cloud) {
3927
+ throw new OllamaGenericClientError(
3928
+ `Operation ${operation.operationId} (${operation.method} ${operation.path}) is local-only and not supported in cloud mode.`
3929
+ );
3930
+ }
3931
+ const minVersion = operation.constraints?.minOllamaVersion;
3932
+ if (!minVersion) return;
3933
+ let serverVersion = this.options.serverVersion;
3934
+ const policy = this.options.enforceVersion ?? "auto";
3935
+ if (!serverVersion && policy !== "off") {
3936
+ if (!this.serverVersionCache) {
3937
+ const p = fetchServerVersion(this.options.http).catch((err) => {
3938
+ if (policy === "strict") {
3939
+ throw new OllamaServerVersionUnknownError(
3940
+ `Operation ${operation.operationId} requires Ollama >= ${minVersion} but the runtime could not determine the server version (enforceVersion: 'strict').`,
3941
+ {
3942
+ operationId: operation.operationId,
3943
+ minRequiredVersion: minVersion,
3944
+ cause: err
3945
+ }
3946
+ );
3947
+ }
3948
+ return void 0;
3949
+ });
3950
+ this.serverVersionCache = p;
3951
+ }
3952
+ serverVersion = await this.serverVersionCache ?? void 0;
3953
+ }
3954
+ if (serverVersion && compareVersions(serverVersion, minVersion) < 0) {
3955
+ throw new OllamaGenericClientError(
3956
+ `Operation ${operation.operationId} (${operation.method} ${operation.path}) requires Ollama >= ${minVersion} (server reports ${serverVersion}).`
3957
+ );
3958
+ }
3959
+ }
3960
+ };
3961
+ function parseStreamByMode(mode, body) {
3962
+ if (mode === "ndjson") {
3963
+ return parseNdjsonStream(body);
3964
+ }
3965
+ if (mode === "sse") {
3966
+ return parseSseStreamAsJson(body);
3967
+ }
3968
+ throw new OllamaGenericClientError(
3969
+ `Transport mode "${mode}" is not a streaming mode; cannot parse a stream for it.`
3970
+ );
3971
+ }
3972
+ async function* parseSseStreamAsJson(body) {
3973
+ for await (const event of parseSseStream(body)) {
3974
+ const data = event.data;
3975
+ if (!data) continue;
3976
+ if (data === "[DONE]") return;
3977
+ try {
3978
+ yield JSON.parse(data);
3979
+ } catch (err) {
3980
+ throw new OllamaGenericClientError(`Failed to parse SSE event data as JSON: ${data}`, {
3981
+ cause: err
3982
+ });
3983
+ }
3984
+ }
3985
+ }
3986
+ function serializeForByteCount(body) {
3987
+ if (body instanceof Uint8Array) return body;
3988
+ if (typeof body === "string") return new TextEncoder().encode(body);
3989
+ const json = JSON.stringify(body);
3990
+ return new TextEncoder().encode(json);
3991
+ }
3992
+ function bodyHasImages(body) {
3993
+ if (typeof body !== "object" || body === null) return false;
3994
+ const obj = body;
3995
+ const images = obj.images;
3996
+ return Array.isArray(images) && images.length > 0;
3997
+ }
3998
+ function resolvePathParams(pathTemplate, pathParams) {
3999
+ if (!pathTemplate.includes("{")) return pathTemplate;
4000
+ const params = pathParams ?? {};
4001
+ return pathTemplate.replace(/\{([^}]+)\}/g, (_match, name) => {
4002
+ const value = params[name];
4003
+ if (value === void 0) {
4004
+ throw new OllamaGenericClientError(
4005
+ `Missing path parameter "${name}" for path template "${pathTemplate}". Provide it via the pathParams field on the invoke() request.`
4006
+ );
4007
+ }
4008
+ return encodeURIComponent(value);
4009
+ });
4010
+ }
4011
+ function extractModelFromBody(body) {
4012
+ if (typeof body !== "object" || body === null) return void 0;
4013
+ const obj = body;
4014
+ const model = obj.model;
4015
+ return typeof model === "string" ? model : void 0;
4016
+ }
4017
+ function applyQueryParams(path, queryParams) {
4018
+ if (!queryParams || Object.keys(queryParams).length === 0) return path;
4019
+ const pairs = Object.entries(queryParams).map(([key, value]) => `${encodeURIComponent(key)}=${encodeURIComponent(value)}`).join("&");
4020
+ return `${path}?${pairs}`;
4021
+ }
4022
+
4023
+ // src/responses.ts
4024
+ var adapterResponseSequence = 0;
4025
+ function toNativeTools(tools) {
4026
+ if (tools === void 0 || tools.length === 0) return void 0;
4027
+ return tools.map((tool) => ({
4028
+ type: "function",
4029
+ function: {
4030
+ name: tool.name,
4031
+ description: tool.description ?? "",
4032
+ parameters: tool.parameters ?? { type: "object", properties: {} }
4033
+ }
4034
+ }));
4035
+ }
4036
+ function mapNativeUsage(usage) {
4037
+ if (usage === void 0) return void 0;
4038
+ const input = usage.input_tokens ?? 0;
4039
+ const output = usage.output_tokens ?? 0;
4040
+ return { input_tokens: input, output_tokens: output, total_tokens: input + output };
4041
+ }
4042
+ function parseToolCallArguments(raw) {
4043
+ if (raw === void 0 || raw === null || raw === "") return {};
4044
+ try {
4045
+ const parsed = JSON.parse(raw);
4046
+ return typeof parsed === "object" && parsed !== null ? parsed : {};
4047
+ } catch {
4048
+ return { _raw: raw };
4049
+ }
4050
+ }
4051
+ function mapNativeResponse(native) {
4052
+ const textParts = [];
4053
+ const thinkingParts = [];
4054
+ const toolCalls = [];
4055
+ for (const item of native.output ?? []) {
4056
+ if (item.type === "message") {
4057
+ for (const part of item.content ?? []) {
4058
+ if (part.type === "output_text") textParts.push(part.text ?? "");
4059
+ else if (part.type === "refusal") textParts.push(part.refusal ?? "");
4060
+ }
4061
+ } else if (item.type === "reasoning") {
4062
+ if (item.text !== void 0 && item.text !== "") thinkingParts.push(item.text);
4063
+ } else if (item.type === "function_call") {
4064
+ toolCalls.push({
4065
+ ...item.id !== void 0 ? { id: item.id } : {},
4066
+ function: { name: item.name, arguments: parseToolCallArguments(item.arguments) }
4067
+ });
4068
+ }
4069
+ }
4070
+ return {
4071
+ id: native.id ?? `resp_${Date.now().toString(36)}`,
4072
+ model: native.model,
4073
+ created_at: new Date((native.created_at ?? 0) * 1e3).toISOString(),
4074
+ output_text: textParts.join(""),
4075
+ ...thinkingParts.length > 0 ? { thinking: thinkingParts.join("\n") } : {},
4076
+ ...toolCalls.length > 0 ? { tool_calls: toolCalls } : {},
4077
+ ...mapNativeUsage(native.usage) !== void 0 ? { usage: mapNativeUsage(native.usage) } : {}
4078
+ };
4079
+ }
4080
+ function mapChatUsage(res) {
4081
+ const asChat = res;
4082
+ const asStream = res;
4083
+ const input = asChat.prompt_eval_count ?? asStream.usage?.promptTokens ?? asStream.raw?.prompt_eval_count ?? 0;
4084
+ const output = asChat.eval_count ?? asStream.usage?.completionTokens ?? asStream.raw?.eval_count ?? 0;
4085
+ if (input === 0 && output === 0) return void 0;
4086
+ return { input_tokens: input, output_tokens: output, total_tokens: input + output };
4087
+ }
4088
+ function mapChatResponse(res) {
4089
+ adapterResponseSequence += 1;
4090
+ return {
4091
+ id: `resp_${Date.now().toString(36)}${adapterResponseSequence.toString(36)}`,
4092
+ model: res.model,
4093
+ created_at: res.created_at,
4094
+ output_text: res.message.content,
4095
+ ...res.message.thinking !== void 0 ? { thinking: res.message.thinking } : {},
4096
+ ...res.message.tool_calls !== void 0 && res.message.tool_calls.length > 0 ? { tool_calls: res.message.tool_calls } : {},
4097
+ ...mapChatUsage(res) !== void 0 ? { usage: mapChatUsage(res) } : {},
4098
+ transport: "chat-adapter"
4099
+ };
4100
+ }
4101
+ var ResponsesModule = class {
4102
+ constructor(runner) {
4103
+ this.runner = runner;
4104
+ }
4105
+ /** The request body sent to the native endpoint (also reused for streaming). */
4106
+ nativeBody(req) {
4107
+ return {
4108
+ model: req.model,
4109
+ input: req.input,
4110
+ ...req.instructions !== void 0 ? { instructions: req.instructions } : {},
4111
+ ...req.tools !== void 0 && req.tools.length > 0 ? { tools: toNativeTools(req.tools) } : {},
4112
+ ...req.temperature !== void 0 ? { temperature: req.temperature } : {},
4113
+ ...req.top_p !== void 0 ? { top_p: req.top_p } : {},
4114
+ ...req.max_output_tokens !== void 0 ? { max_output_tokens: req.max_output_tokens } : {},
4115
+ ...req.reasoning_effort !== void 0 ? { reasoning: { effort: req.reasoning_effort } } : {},
4116
+ ...req.think !== void 0 ? { think: req.think } : {}
4117
+ };
4118
+ }
4119
+ /**
4120
+ * Runs one Responses request, preferring the native `POST /v1/responses`
4121
+ * endpoint and transparently re-issuing via `/api/chat` when the server
4122
+ * answers `404` (pre-v0.13.3 Ollama without the compatibility endpoint).
4123
+ */
4124
+ async create(req) {
4125
+ const body = this.nativeBody(req);
4126
+ try {
4127
+ const native = await this.runner(
4128
+ (http, signal) => http.request({
4129
+ path: "/v1/responses",
4130
+ body: { ...body, stream: false },
4131
+ signal
4132
+ }),
4133
+ { model: req.model, signal: req.signal, timeoutMs: req.timeoutMs }
4134
+ );
4135
+ return { ...mapNativeResponse(native), transport: "native" };
4136
+ } catch (err) {
4137
+ if (!(err instanceof OllamaNotFoundError)) throw err;
4138
+ }
4139
+ return this.createViaChatAdapter(req);
4140
+ }
4141
+ /** Convenience: `create()` returning only the `output_text` string. */
4142
+ async createText(req) {
4143
+ const res = await this.create(req);
4144
+ return res.output_text;
4145
+ }
4146
+ async createViaChatAdapter(req) {
4147
+ const messages = [
4148
+ ...req.instructions !== void 0 ? [{ role: "system", content: req.instructions }] : [],
4149
+ { role: "user", content: req.input }
4150
+ ];
4151
+ const chatRes = await this.runner(
4152
+ (http, signal) => http.request({
4153
+ path: "/api/chat",
4154
+ body: {
4155
+ model: req.model,
4156
+ messages,
4157
+ ...req.tools !== void 0 && req.tools.length > 0 ? { tools: toNativeTools(req.tools) } : {},
4158
+ options: {
4159
+ ...req.temperature !== void 0 ? { temperature: req.temperature } : {},
4160
+ ...req.top_p !== void 0 ? { top_p: req.top_p } : {},
4161
+ ...req.max_output_tokens !== void 0 ? { num_predict: req.max_output_tokens } : {}
4162
+ },
4163
+ ...req.think !== void 0 ? { think: req.think } : {},
4164
+ stream: false
4165
+ },
4166
+ signal
4167
+ }),
4168
+ { model: req.model, signal: req.signal, timeoutMs: req.timeoutMs }
4169
+ );
4170
+ return mapChatResponse(chatRes);
4171
+ }
4172
+ /**
4173
+ * Streams a Responses request as simplified
4174
+ * `{type: 'text_delta' | 'thinking_delta' | 'done'}` events. Dual-mode like
4175
+ * {@link create}: native SSE from `/v1/responses` when available, otherwise
4176
+ * the normalized `/api/chat` token stream. Tool calls are not delta'd —
4177
+ * collect them from the final `done` event's `response.tool_calls`.
4178
+ *
4179
+ * This is a lazy async generator: nothing is sent until the first
4180
+ * iteration, matching `for await (... of client.responses.stream(req))`
4181
+ * intuition. The generator is also cancellable through `req.signal`.
4182
+ */
4183
+ async *stream(req) {
4184
+ const body = this.nativeBody(req);
4185
+ let nativeStream;
4186
+ try {
4187
+ nativeStream = await this.runner(
4188
+ async (http, signal) => {
4189
+ const source = await http.requestSseStream({
4190
+ path: "/v1/responses",
4191
+ body: { ...body, stream: true },
4192
+ signal
4193
+ });
4194
+ return new OpenAIResponsesStream(source, signal);
4195
+ },
4196
+ {
4197
+ model: req.model,
4198
+ signal: req.signal,
4199
+ timeoutMs: req.timeoutMs,
4200
+ holdUntil: (s) => s.finalResult
4201
+ }
4202
+ );
4203
+ } catch (err) {
4204
+ if (!(err instanceof OllamaNotFoundError)) throw err;
4205
+ }
4206
+ if (nativeStream !== void 0) {
4207
+ for await (const event of nativeStream) {
4208
+ const type = event.type;
4209
+ if (type === "response.output_text.delta") {
4210
+ yield { type: "text_delta", delta: event.delta };
4211
+ } else if (type === "response.reasoning_text.delta") {
4212
+ yield { type: "thinking_delta", delta: event.delta };
4213
+ }
4214
+ }
4215
+ yield {
4216
+ type: "done",
4217
+ response: { ...mapNativeResponse(await nativeStream.finalResult), transport: "native" }
4218
+ };
4219
+ return;
4220
+ }
4221
+ const chatStream = await this.runner(
4222
+ async (http, signal) => {
4223
+ const source = await http.requestStream({
4224
+ path: "/api/chat",
4225
+ body: {
4226
+ model: req.model,
4227
+ messages: [
4228
+ ...req.instructions !== void 0 ? [{ role: "system", content: req.instructions }] : [],
4229
+ { role: "user", content: req.input }
4230
+ ],
4231
+ ...req.tools !== void 0 && req.tools.length > 0 ? { tools: toNativeTools(req.tools) } : {},
4232
+ options: {
4233
+ ...req.temperature !== void 0 ? { temperature: req.temperature } : {},
4234
+ ...req.top_p !== void 0 ? { top_p: req.top_p } : {},
4235
+ ...req.max_output_tokens !== void 0 ? { num_predict: req.max_output_tokens } : {}
4236
+ },
4237
+ ...req.think !== void 0 ? { think: req.think } : {},
4238
+ stream: true
4239
+ },
4240
+ signal
4241
+ });
4242
+ return normalizeChatStream(source, signal);
4243
+ },
4244
+ {
4245
+ model: req.model,
4246
+ signal: req.signal,
4247
+ timeoutMs: req.timeoutMs,
4248
+ holdUntil: (s) => s.finalResult
4249
+ }
4250
+ );
4251
+ for await (const event of chatStream) {
4252
+ if (event.type === "token") {
4253
+ yield { type: "text_delta", delta: event.data.delta };
4254
+ } else if (event.type === "thinking") {
4255
+ yield { type: "thinking_delta", delta: event.data.delta };
4256
+ }
4257
+ }
4258
+ const result = await chatStream.finalResult;
4259
+ yield {
4260
+ type: "done",
4261
+ response: {
4262
+ id: `resp_${Date.now().toString(36)}${(++adapterResponseSequence).toString(36)}`,
4263
+ model: result.model,
4264
+ created_at: (/* @__PURE__ */ new Date()).toISOString(),
4265
+ output_text: result.message.content,
4266
+ ...result.message.thinking !== void 0 ? { thinking: result.message.thinking } : {},
4267
+ ...result.message.tool_calls !== void 0 && result.message.tool_calls.length > 0 ? { tool_calls: result.message.tool_calls } : {},
4268
+ ...mapChatUsage(result) !== void 0 ? { usage: mapChatUsage(result) } : {},
4269
+ transport: "chat-adapter"
4270
+ }
4271
+ };
4272
+ }
4273
+ };
4274
+
4275
+ // src/conversation.ts
4276
+ function extractTurnStats(res) {
4277
+ const cachedTokens = res.prompt_eval_cached_count ?? 0;
4278
+ const evaluatedTokens = res.prompt_eval_count ?? 0;
4279
+ const total = cachedTokens + evaluatedTokens;
4280
+ return {
4281
+ cachedTokens,
4282
+ evaluatedTokens,
4283
+ hitRate: total > 0 ? cachedTokens / total : 0
4284
+ };
4285
+ }
4286
+ var ConversationSession = class {
4287
+ constructor(client, config) {
4288
+ this.client = client;
4289
+ this.config = config;
4290
+ if (config.systemPrompt !== void 0 && config.systemPrompt !== "") {
4291
+ this.history.push(Object.freeze({ role: "system", content: config.systemPrompt }));
4292
+ }
4293
+ }
4294
+ /** Ordered, append-only history. Message objects are frozen on push. */
4295
+ history = [];
4296
+ /** Mutable tallies behind the read-only {@link cacheStats} view. */
4297
+ tallies = {
4298
+ turns: 0,
4299
+ cachedTokens: 0,
4300
+ evaluatedTokens: 0
4301
+ };
4302
+ /** The model name this session talks to. */
4303
+ get model() {
4304
+ return this.config.model;
4305
+ }
4306
+ /** Number of completed turns. */
4307
+ get turnCount() {
4308
+ return this.tallies.turns;
4309
+ }
4310
+ /** Cumulative KV-cache statistics across completed turns. */
4311
+ get cacheStats() {
4312
+ const total = this.tallies.cachedTokens + this.tallies.evaluatedTokens;
4313
+ return {
4314
+ turns: this.tallies.turns,
4315
+ cachedTokens: this.tallies.cachedTokens,
4316
+ evaluatedTokens: this.tallies.evaluatedTokens,
4317
+ totalPromptTokens: total,
4318
+ hitRate: total > 0 ? this.tallies.cachedTokens / total : 0
4319
+ };
4320
+ }
4321
+ /**
4322
+ * Sends the user's message and returns the assistant's reply text —
4323
+ * the ergonomic path. Use {@link sendTurn} when you also need the
4324
+ * per-turn cache statistics or the raw response.
4325
+ */
4326
+ async send(content, overrides) {
4327
+ const turn = await this.sendTurn(content, overrides);
4328
+ return turn.content;
4329
+ }
4330
+ /**
4331
+ * Sends the user's message and returns the full turn record: reply text,
4332
+ * per-turn KV-cache statistics, and the raw `/api/chat` response.
4333
+ */
4334
+ async sendTurn(content, overrides) {
4335
+ this.history.push(Object.freeze({ role: "user", content }));
4336
+ const response = await this.client.chat({
4337
+ model: this.config.model,
4338
+ messages: [...this.history],
4339
+ ...this.config.tools !== void 0 || overrides?.tools !== void 0 ? { tools: overrides?.tools ?? this.config.tools } : {},
4340
+ ...this.config.format !== void 0 || overrides?.format !== void 0 ? { format: overrides?.format ?? this.config.format } : {},
4341
+ options: { ...this.config.options, ...overrides?.options },
4342
+ ...this.config.think !== void 0 || overrides?.think !== void 0 ? { think: overrides?.think ?? this.config.think } : {},
4343
+ ...this.config.keep_alive !== void 0 || overrides?.keep_alive !== void 0 ? { keep_alive: overrides?.keep_alive ?? this.config.keep_alive } : {},
4344
+ ...overrides?.signal !== void 0 ? { signal: overrides.signal } : {},
4345
+ ...overrides?.timeoutMs !== void 0 ? { timeoutMs: overrides.timeoutMs } : {},
4346
+ stream: false
4347
+ });
4348
+ this.history.push(Object.freeze({ ...response.message }));
4349
+ const cache = extractTurnStats(response);
4350
+ this.tallies.turns += 1;
4351
+ this.tallies.cachedTokens += cache.cachedTokens;
4352
+ this.tallies.evaluatedTokens += cache.evaluatedTokens;
4353
+ return { content: response.message.content, cache, response };
4354
+ }
4355
+ /**
4356
+ * A defensive copy of the session history — mutating the returned array or
4357
+ * its frozen messages has no effect on the session.
4358
+ */
4359
+ getMessages() {
4360
+ return this.history.map((message) => message);
4361
+ }
4362
+ /**
4363
+ * Clears the conversation back to its initial state (system prompt, zero
4364
+ * turns, zeroed cache statistics). The next turn starts with a cold cache
4365
+ * by definition — there is nothing upstream of it to reuse.
4366
+ */
4367
+ reset() {
4368
+ this.history.length = 0;
4369
+ if (this.config.systemPrompt !== void 0 && this.config.systemPrompt !== "") {
4370
+ this.history.push(
4371
+ Object.freeze({ role: "system", content: this.config.systemPrompt })
4372
+ );
4373
+ }
4374
+ this.tallies.turns = 0;
4375
+ this.tallies.cachedTokens = 0;
4376
+ this.tallies.evaluatedTokens = 0;
2979
4377
  }
2980
4378
  };
2981
4379
 
4380
+ // src/context-safety.ts
4381
+ var DEFAULT_CONTEXT_WARNING_THRESHOLD = 0.9;
4382
+ var IMAGE_TOKEN_ESTIMATE = 300;
4383
+ var CHARS_PER_TOKEN = 4;
4384
+ function isCjkCodePoint(code) {
4385
+ return code >= 19968 && code <= 40959 || // CJK Unified Ideographs
4386
+ code >= 13312 && code <= 19903 || // CJK Extension A
4387
+ code >= 12352 && code <= 12543 || // Hiragana + Katakana
4388
+ code >= 44032 && code <= 55215 || // Hangul syllables
4389
+ code >= 63744 && code <= 64255 || // CJK Compatibility Ideographs
4390
+ code >= 65280 && code <= 65519;
4391
+ }
4392
+ function estimateTokens(text) {
4393
+ if (text.length === 0) return 0;
4394
+ let cjk = 0;
4395
+ let other = 0;
4396
+ for (const char of text) {
4397
+ if (isCjkCodePoint(char.codePointAt(0) ?? 0)) cjk += 1;
4398
+ else other += 1;
4399
+ }
4400
+ return Math.ceil(cjk + other / CHARS_PER_TOKEN);
4401
+ }
4402
+ var MESSAGE_OVERHEAD_TOKENS = 4;
4403
+ function estimateMessageTokens(message) {
4404
+ let total = MESSAGE_OVERHEAD_TOKENS + estimateTokens(message.content ?? "");
4405
+ if (message.thinking !== void 0) total += estimateTokens(message.thinking);
4406
+ if (message.tool_calls !== void 0) {
4407
+ for (const call of message.tool_calls) {
4408
+ total += estimateTokens(call.function.name);
4409
+ total += estimateTokens(JSON.stringify(call.function.arguments ?? {}));
4410
+ }
4411
+ }
4412
+ if (message.images !== void 0) total += message.images.length * IMAGE_TOKEN_ESTIMATE;
4413
+ return total;
4414
+ }
4415
+ function estimateChatRequestTokens(req) {
4416
+ let total = 0;
4417
+ for (const message of req.messages) total += estimateMessageTokens(message);
4418
+ if (req.tools !== void 0) {
4419
+ for (const tool of req.tools) {
4420
+ total += estimateTokens(tool.function.description ?? "");
4421
+ total += estimateTokens(JSON.stringify(tool.function.parameters ?? {}));
4422
+ }
4423
+ }
4424
+ return total;
4425
+ }
4426
+ function estimateGenerateRequestTokens(req) {
4427
+ let total = estimateTokens(req.prompt ?? "");
4428
+ if (req.system !== void 0) total += estimateTokens(req.system);
4429
+ if (req.template !== void 0) total += estimateTokens(req.template);
4430
+ if (req.images !== void 0) total += req.images.length * IMAGE_TOKEN_ESTIMATE;
4431
+ return total;
4432
+ }
4433
+ function checkChatContext(req, options = {}) {
4434
+ return check(estimateChatRequestTokens(req), req.options?.num_ctx, options);
4435
+ }
4436
+ function checkGenerateContext(req, options = {}) {
4437
+ return check(estimateGenerateRequestTokens(req), req.options?.num_ctx, options);
4438
+ }
4439
+ function check(estimatedPromptTokens, requestNumCtx, options) {
4440
+ const threshold = options.threshold ?? DEFAULT_CONTEXT_WARNING_THRESHOLD;
4441
+ if (requestNumCtx !== void 0) {
4442
+ return {
4443
+ estimatedPromptTokens,
4444
+ effectiveContextLength: requestNumCtx,
4445
+ windowSource: "request",
4446
+ exceedsThreshold: estimatedPromptTokens > requestNumCtx * threshold
4447
+ };
4448
+ }
4449
+ if (options.defaultContextLength !== void 0) {
4450
+ return {
4451
+ estimatedPromptTokens,
4452
+ effectiveContextLength: options.defaultContextLength,
4453
+ windowSource: "client-default",
4454
+ exceedsThreshold: estimatedPromptTokens > options.defaultContextLength * threshold
4455
+ };
4456
+ }
4457
+ return {
4458
+ estimatedPromptTokens,
4459
+ effectiveContextLength: OLLAMA_FALLBACK_CONTEXT_LENGTH,
4460
+ windowSource: "server-fallback",
4461
+ exceedsThreshold: estimatedPromptTokens > OLLAMA_FALLBACK_CONTEXT_LENGTH
4462
+ };
4463
+ }
4464
+ var OLLAMA_FALLBACK_CONTEXT_LENGTH = 2048;
4465
+ function contextWarningMessage(check2, kind) {
4466
+ const where = `OllamaClient.${kind}()`;
4467
+ const estimate = `~${check2.estimatedPromptTokens} tokens`;
4468
+ if (check2.windowSource === "request") {
4469
+ return `${where}: estimated prompt size ${estimate} is close to or beyond the request's context window (num_ctx=${check2.effectiveContextLength}). The server will likely silently truncate the top of the conversation \u2014 the model loses earlier turns with no error. Raise \`options.num_ctx\` or shorten the conversation. Estimates are heuristic (\xB120\u201330%); see \`estimateTokens\`.`;
4470
+ }
4471
+ if (check2.windowSource === "client-default") {
4472
+ return `${where}: estimated prompt size ${estimate} is close to or beyond the client's \`defaultContextLength\` (${check2.effectiveContextLength}, injected as \`num_ctx\`). The server will likely silently truncate the top of the conversation \u2014 the model loses earlier turns with no error. Raise \`defaultContextLength\`, set \`options.num_ctx\` per request, or shorten the conversation. Estimates are heuristic (\xB120\u201330%); see \`estimateTokens\`.`;
4473
+ }
4474
+ return `${where}: estimated prompt size ${estimate} outright exceeds Ollama's conservative server-default context window (${OLLAMA_FALLBACK_CONTEXT_LENGTH}\u20134096 tokens when \`num_ctx\` is unset \u2014 model-dependent). Unless the model's default is larger, the server will silently truncate the top of the conversation \u2014 the model loses earlier turns with no error. Set \`options.num_ctx\` (or the client's \`defaultContextLength\`) to an explicit window to make truncation visible and controllable. Estimates are heuristic (\xB120\u201330%); see \`estimateTokens\`.`;
4475
+ }
4476
+
4477
+ // src/generated/api/operations.ts
4478
+ var blobsOp = {
4479
+ operationId: "blobs",
4480
+ method: "HEAD",
4481
+ path: "/api/blobs/{digest}",
4482
+ environment: { local: true, cloud: false },
4483
+ parameters: [{ name: "digest", in: "path", required: true, schema: { type: "string" } }],
4484
+ transport: { mode: "json", streaming: false },
4485
+ capabilities: {},
4486
+ status: { documented: true },
4487
+ domain: "native",
4488
+ notes: [
4489
+ "HEAD-only endpoint for blob existence checks; declared in OpenAPI but not in the legacy parity manifest."
4490
+ ]
4491
+ };
4492
+ var chatOp = {
4493
+ operationId: "chat",
4494
+ method: "POST",
4495
+ path: "/api/chat",
4496
+ request: { $ref: "#/schemas/ChatRequest" },
4497
+ response: { $ref: "#/schemas/ChatResponse" },
4498
+ environment: { local: true, cloud: true },
4499
+ transport: { mode: "ndjson", streaming: true, streamingDefault: true },
4500
+ capabilities: {
4501
+ thinking: "model-dependent",
4502
+ tools: "model-dependent",
4503
+ vision: "model-dependent",
4504
+ structuredOutput: "supported",
4505
+ logprobs: "supported"
4506
+ },
4507
+ status: { documented: true },
4508
+ domain: "native",
4509
+ notes: [
4510
+ "Use /api/show to discover model-specific thinking values and defaults.",
4511
+ "Structured output (format field) is rejected by Ollama Cloud."
4512
+ ]
4513
+ };
4514
+ var copyOp = {
4515
+ operationId: "copy",
4516
+ method: "POST",
4517
+ path: "/api/copy",
4518
+ request: { $ref: "#/schemas/CopyRequest" },
4519
+ environment: { local: true, cloud: false },
4520
+ transport: { mode: "json", streaming: false },
4521
+ capabilities: {},
4522
+ status: { documented: true },
4523
+ domain: "native"
4524
+ };
4525
+ var createOp = {
4526
+ operationId: "create",
4527
+ method: "POST",
4528
+ path: "/api/create",
4529
+ request: { $ref: "#/schemas/CreateRequest" },
4530
+ response: { $ref: "#/schemas/StatusResponse" },
4531
+ environment: { local: true, cloud: false },
4532
+ transport: { mode: "ndjson", streaming: true, streamingDefault: true },
4533
+ capabilities: { thinking: "unsupported", tools: "unsupported" },
4534
+ status: { documented: true },
4535
+ domain: "native",
4536
+ notes: ["Model creation is local-only; Ollama Cloud does not support it."]
4537
+ };
4538
+ var deleteOp = {
4539
+ operationId: "delete",
4540
+ method: "DELETE",
4541
+ path: "/api/delete",
4542
+ request: { $ref: "#/schemas/DeleteRequest" },
4543
+ environment: { local: true, cloud: false },
4544
+ transport: { mode: "json", streaming: false },
4545
+ capabilities: {},
4546
+ status: { documented: true },
4547
+ domain: "native"
4548
+ };
4549
+ var embedOp = {
4550
+ operationId: "embed",
4551
+ method: "POST",
4552
+ path: "/api/embed",
4553
+ request: { $ref: "#/schemas/EmbedRequest" },
4554
+ response: { $ref: "#/schemas/EmbedResponse" },
4555
+ environment: { local: true, cloud: true },
4556
+ transport: { mode: "json", streaming: false },
4557
+ capabilities: { embeddings: "supported" },
4558
+ status: { documented: true },
4559
+ domain: "native"
4560
+ };
4561
+ var generateOp = {
4562
+ operationId: "generate",
4563
+ method: "POST",
4564
+ path: "/api/generate",
4565
+ request: { $ref: "#/schemas/GenerateRequest" },
4566
+ response: { $ref: "#/schemas/GenerateResponse" },
4567
+ environment: { local: true, cloud: true },
4568
+ transport: { mode: "ndjson", streaming: true, streamingDefault: true },
4569
+ capabilities: {
4570
+ thinking: "model-dependent",
4571
+ vision: "supported",
4572
+ structuredOutput: "supported",
4573
+ logprobs: "supported"
4574
+ },
4575
+ status: { documented: true },
4576
+ domain: "native"
4577
+ };
4578
+ var psOp = {
4579
+ operationId: "ps",
4580
+ method: "GET",
4581
+ path: "/api/ps",
4582
+ response: { $ref: "#/schemas/PsResponse" },
4583
+ environment: { local: true, cloud: true },
4584
+ transport: { mode: "json", streaming: false },
4585
+ capabilities: {},
4586
+ status: { documented: true },
4587
+ domain: "native"
4588
+ };
4589
+ var pullOp = {
4590
+ operationId: "pull",
4591
+ method: "POST",
4592
+ path: "/api/pull",
4593
+ request: { $ref: "#/schemas/PullRequest" },
4594
+ response: { $ref: "#/schemas/StatusResponse" },
4595
+ environment: { local: true, cloud: false },
4596
+ transport: { mode: "ndjson", streaming: true, streamingDefault: true },
4597
+ capabilities: {},
4598
+ status: { documented: true },
4599
+ domain: "native"
4600
+ };
4601
+ var pushOp = {
4602
+ operationId: "push",
4603
+ method: "POST",
4604
+ path: "/api/push",
4605
+ request: { $ref: "#/schemas/PushRequest" },
4606
+ response: { $ref: "#/schemas/StatusResponse" },
4607
+ environment: { local: true, cloud: false },
4608
+ transport: { mode: "ndjson", streaming: true, streamingDefault: true },
4609
+ capabilities: {},
4610
+ status: { documented: true },
4611
+ domain: "native"
4612
+ };
4613
+ var showOp = {
4614
+ operationId: "show",
4615
+ method: "POST",
4616
+ path: "/api/show",
4617
+ request: { $ref: "#/schemas/ShowRequest" },
4618
+ response: { $ref: "#/schemas/ShowResponse" },
4619
+ environment: { local: true, cloud: true },
4620
+ transport: { mode: "json", streaming: false },
4621
+ capabilities: {},
4622
+ status: { documented: true },
4623
+ domain: "native",
4624
+ notes: [
4625
+ "Capabilities source: /api/show is the runtime source of truth for model-dependent capability values (tools, vision, thinking, embeddings)."
4626
+ ]
4627
+ };
4628
+ var systemOneOp = {
4629
+ operationId: "systemOne",
4630
+ method: "POST",
4631
+ path: "/v1/systemone",
4632
+ request: { $ref: "#/schemas/SystemOneRequest" },
4633
+ response: { $ref: "#/schemas/SystemOneResponse" },
4634
+ environment: { local: true, cloud: false },
4635
+ transport: { mode: "json", streaming: false },
4636
+ capabilities: {
4637
+ thinking: "unsupported",
4638
+ tools: "unsupported",
4639
+ vision: "model-dependent",
4640
+ structuredOutput: "unsupported",
4641
+ logprobs: "unsupported",
4642
+ embeddings: "unsupported"
4643
+ },
4644
+ constraints: {
4645
+ minOllamaVersion: "0.35.0",
4646
+ maxRequestBytes: 65536,
4647
+ maxRequestBytesWithImages: 33554432
4648
+ },
4649
+ status: { documented: true, experimental: true },
4650
+ domain: "native",
4651
+ notes: [
4652
+ "Local-only; Ollama Cloud rejects /v1/systemone.",
4653
+ "64 KiB request limit without images, 32 MiB with images (includes JSON/base64 overhead).",
4654
+ "No tools, no generation controls (temperature, top_p, etc.).",
4655
+ "Vision is model-dependent: images are accepted by vision-weighted models (Clef/Clef Flash) but not by text-only models.",
4656
+ "Experimental: behavioral surface may change between Ollama releases."
4657
+ ]
4658
+ };
4659
+ var tagsOp = {
4660
+ operationId: "tags",
4661
+ method: "GET",
4662
+ path: "/api/tags",
4663
+ response: { $ref: "#/schemas/ListResponse" },
4664
+ environment: { local: true, cloud: true },
4665
+ transport: { mode: "json", streaming: false },
4666
+ capabilities: {},
4667
+ status: { documented: true },
4668
+ domain: "native"
4669
+ };
4670
+ var versionOp = {
4671
+ operationId: "version",
4672
+ method: "GET",
4673
+ path: "/api/version",
4674
+ response: { $ref: "#/schemas/VersionResponse" },
4675
+ environment: { local: true, cloud: true },
4676
+ transport: { mode: "json", streaming: false },
4677
+ capabilities: {},
4678
+ status: { documented: true },
4679
+ domain: "native"
4680
+ };
4681
+
4682
+ // src/generated/api/native-api.ts
4683
+ var NativeApi = class {
4684
+ constructor(runtime) {
4685
+ this.runtime = runtime;
4686
+ }
4687
+ blobs(digest, options) {
4688
+ return this.runtime.invoke({
4689
+ operation: blobsOp,
4690
+ body: void 0,
4691
+ pathParams: { digest },
4692
+ ...options?.signal !== void 0 ? { signal: options.signal } : {}
4693
+ });
4694
+ }
4695
+ chat(request) {
4696
+ return this.runtime.invoke({
4697
+ operation: chatOp,
4698
+ body: request,
4699
+ streamingDefault: true
4700
+ });
4701
+ }
4702
+ copy(request) {
4703
+ return this.runtime.invoke({
4704
+ operation: copyOp,
4705
+ body: request
4706
+ });
4707
+ }
4708
+ create(request) {
4709
+ return this.runtime.invoke({
4710
+ operation: createOp,
4711
+ body: request,
4712
+ streamingDefault: true
4713
+ });
4714
+ }
4715
+ delete(request) {
4716
+ return this.runtime.invoke({
4717
+ operation: deleteOp,
4718
+ body: request
4719
+ });
4720
+ }
4721
+ embed(request) {
4722
+ return this.runtime.invoke({
4723
+ operation: embedOp,
4724
+ body: request
4725
+ });
4726
+ }
4727
+ generate(request) {
4728
+ return this.runtime.invoke({
4729
+ operation: generateOp,
4730
+ body: request,
4731
+ streamingDefault: true
4732
+ });
4733
+ }
4734
+ ps(options) {
4735
+ return this.runtime.invoke({
4736
+ operation: psOp,
4737
+ body: void 0,
4738
+ ...options?.signal !== void 0 ? { signal: options.signal } : {}
4739
+ });
4740
+ }
4741
+ pull(request) {
4742
+ return this.runtime.invoke({
4743
+ operation: pullOp,
4744
+ body: request,
4745
+ streamingDefault: true
4746
+ });
4747
+ }
4748
+ push(request) {
4749
+ return this.runtime.invoke({
4750
+ operation: pushOp,
4751
+ body: request,
4752
+ streamingDefault: true
4753
+ });
4754
+ }
4755
+ show(request) {
4756
+ return this.runtime.invoke({
4757
+ operation: showOp,
4758
+ body: request
4759
+ });
4760
+ }
4761
+ systemOne(request) {
4762
+ return this.runtime.invoke({
4763
+ operation: systemOneOp,
4764
+ body: request
4765
+ });
4766
+ }
4767
+ tags(options) {
4768
+ return this.runtime.invoke({
4769
+ operation: tagsOp,
4770
+ body: void 0,
4771
+ ...options?.signal !== void 0 ? { signal: options.signal } : {}
4772
+ });
4773
+ }
4774
+ version(options) {
4775
+ return this.runtime.invoke({
4776
+ operation: versionOp,
4777
+ body: void 0,
4778
+ ...options?.signal !== void 0 ? { signal: options.signal } : {}
4779
+ });
4780
+ }
4781
+ };
4782
+
4783
+ // src/failover-http-client.ts
4784
+ var FailoverHttpClient = class {
4785
+ constructor(client, baseUrl) {
4786
+ this.client = client;
4787
+ this.baseUrl = baseUrl;
4788
+ }
4789
+ /**
4790
+ * The first candidate endpoint's base URL. Used for inferRuntimeMode()
4791
+ * checks (local vs cloud). The actual request routing picks the best
4792
+ * healthy endpoint per call.
4793
+ */
4794
+ baseUrl;
4795
+ /**
4796
+ * Route a request through OllamaClient's failover machinery.
4797
+ *
4798
+ * Each call to request() goes through executeWithFailover, which:
4799
+ * - Picks the best healthy endpoint from the registry
4800
+ * - Applies retry/backoff within that endpoint
4801
+ * - Fails over to the next candidate on retryable errors
4802
+ * - Reports success/failure to the endpoint health tracker
4803
+ *
4804
+ * Wave 15 (P1): the model is extracted from the request body by the
4805
+ * runtime and passed via the `model` field on the options. This lets
4806
+ * the failover layer filter endpoints by `OllamaEndpoint.models`
4807
+ * (credential-scoped routing). If no model is provided, all endpoints
4808
+ * are considered.
4809
+ *
4810
+ * The `raw` option (for streaming responses) is passed through
4811
+ * unchanged — executeWithFailover returns whatever the operation
4812
+ * callback returns, including raw Response objects.
4813
+ */
4814
+ async request(options) {
4815
+ const { model, ...httpOptions } = options;
4816
+ return this.client.executeWithFailover(
4817
+ async (http, signal) => {
4818
+ const mergedOptions = {
4819
+ ...httpOptions,
4820
+ ...httpOptions.signal !== void 0 ? { signal } : { signal }
4821
+ };
4822
+ return http.request(mergedOptions);
4823
+ },
4824
+ {
4825
+ singleEndpoint: false,
4826
+ ...model !== void 0 ? { model } : {}
4827
+ }
4828
+ );
4829
+ }
4830
+ };
4831
+
4832
+ // src/decision.ts
4833
+ function createDecision(client) {
4834
+ return {
4835
+ async choice(params) {
4836
+ const response = await client.systemOne({
4837
+ model: params.model,
4838
+ state: params.state,
4839
+ ...params.images ? { images: params.images } : {},
4840
+ questions: {
4841
+ decision: {
4842
+ type: "choice",
4843
+ instructions: params.instructions,
4844
+ criteria: params.criteria
4845
+ }
4846
+ },
4847
+ ...params.keepAlive !== void 0 ? { keep_alive: params.keepAlive } : {}
4848
+ });
4849
+ const answer = response.answers.decision;
4850
+ return {
4851
+ choice: answer.choice,
4852
+ probabilities: answer.probabilities,
4853
+ confidence: answer.confidence,
4854
+ usage: response.usage
4855
+ };
4856
+ },
4857
+ async noul(params) {
4858
+ const response = await client.systemOne({
4859
+ model: params.model,
4860
+ state: params.state,
4861
+ ...params.images ? { images: params.images } : {},
4862
+ questions: {
4863
+ decision: {
4864
+ type: "noul",
4865
+ instructions: params.instructions
4866
+ }
4867
+ },
4868
+ ...params.keepAlive !== void 0 ? { keep_alive: params.keepAlive } : {}
4869
+ });
4870
+ const answer = response.answers.decision;
4871
+ return {
4872
+ noul: answer.noul,
4873
+ bool: answer.noul >= 0.5,
4874
+ usage: response.usage
4875
+ };
4876
+ },
4877
+ async score(params) {
4878
+ const response = await client.systemOne({
4879
+ model: params.model,
4880
+ state: params.state,
4881
+ ...params.images ? { images: params.images } : {},
4882
+ questions: {
4883
+ decision: {
4884
+ type: "score",
4885
+ instructions: params.instructions,
4886
+ criteria: params.criteria
4887
+ }
4888
+ },
4889
+ ...params.keepAlive !== void 0 ? { keep_alive: params.keepAlive } : {}
4890
+ });
4891
+ const answer = response.answers.decision;
4892
+ return {
4893
+ score: answer.score,
4894
+ legend: answer.legend,
4895
+ probabilities: answer.probabilities,
4896
+ confidence: answer.confidence,
4897
+ usage: response.usage
4898
+ };
4899
+ },
4900
+ async route(params) {
4901
+ const response = await client.systemOne({
4902
+ model: params.model,
4903
+ state: params.state,
4904
+ ...params.images ? { images: params.images } : {},
4905
+ questions: {
4906
+ decision: {
4907
+ type: "choice",
4908
+ instructions: params.instructions,
4909
+ criteria: params.criteria
4910
+ }
4911
+ },
4912
+ ...params.keepAlive !== void 0 ? { keep_alive: params.keepAlive } : {}
4913
+ });
4914
+ const answer = response.answers.decision;
4915
+ return {
4916
+ route: answer.choice,
4917
+ confidence: answer.confidence
4918
+ };
4919
+ },
4920
+ async verify(params) {
4921
+ const state = {
4922
+ claim: params.claim,
4923
+ evidence: params.evidence
4924
+ };
4925
+ const response = await client.systemOne({
4926
+ model: params.model,
4927
+ state,
4928
+ ...params.images ? { images: params.images } : {},
4929
+ questions: {
4930
+ decision: {
4931
+ type: "noul",
4932
+ instructions: params.instructions ?? "Is the claim supported by the evidence?"
4933
+ }
4934
+ },
4935
+ ...params.keepAlive !== void 0 ? { keep_alive: params.keepAlive } : {}
4936
+ });
4937
+ const answer = response.answers.decision;
4938
+ return {
4939
+ verified: answer.noul >= 0.5,
4940
+ probability: answer.noul
4941
+ };
4942
+ },
4943
+ async rank(params) {
4944
+ const promises = params.candidates.map(async (candidate) => {
4945
+ const response = await client.systemOne({
4946
+ model: params.model,
4947
+ state: candidate.state,
4948
+ ...params.images ? { images: params.images } : {},
4949
+ questions: {
4950
+ decision: {
4951
+ type: "score",
4952
+ instructions: params.instructions,
4953
+ criteria: params.criteria
4954
+ }
4955
+ },
4956
+ ...params.keepAlive !== void 0 ? { keep_alive: params.keepAlive } : {}
4957
+ });
4958
+ const answer = response.answers.decision;
4959
+ return {
4960
+ candidate: candidate.id,
4961
+ score: answer.score,
4962
+ confidence: answer.confidence
4963
+ };
4964
+ });
4965
+ const results = await Promise.all(promises);
4966
+ return results.sort((a, b) => b.score - a.score);
4967
+ }
4968
+ };
4969
+ }
4970
+
2982
4971
  // src/client.ts
2983
4972
  var logicalRequestSequence = 0;
2984
4973
  function createLogicalRequestId() {
@@ -3003,14 +4992,21 @@ var OllamaClient = class {
3003
4992
  * `OLLAMA_API_KEY`), independent of whether `config.endpoints` was used instead.
3004
4993
  */
3005
4994
  cloudApiKey;
4995
+ /** Configured `defaultContextLength` — injected as `num_ctx` when a request omits it. */
4996
+ defaultContextLength;
4997
+ /** Configured `contextWarningThreshold` (default 0.9). */
4998
+ contextWarningThreshold;
4999
+ /** Configured overflow policy — `'warn'` (default) or `'throw'`. */
5000
+ onContextOverflow;
3006
5001
  constructor(config = {}) {
3007
- const resolvedApiKey = resolveApiKey(config.apiKey);
3008
- this.cloudApiKey = resolvedApiKey;
5002
+ this.cloudApiKey = resolveApiKey(config.apiKey);
5003
+ const defaultBaseUrl = resolveBaseUrl(config.baseUrl);
5004
+ const resolvedApiKey = resolveEndpointApiKey(config.apiKey, defaultBaseUrl);
3009
5005
  const credentialEndpoints = resolveCredentialEndpoints(config);
3010
5006
  const endpoints = config.endpoints ?? (credentialEndpoints.length > 0 ? [] : [
3011
5007
  {
3012
5008
  name: "default",
3013
- baseUrl: resolveBaseUrl(config.baseUrl),
5009
+ baseUrl: defaultBaseUrl,
3014
5010
  ...resolvedApiKey !== void 0 ? { apiKey: resolvedApiKey } : {},
3015
5011
  ...config.headers !== void 0 ? { headers: config.headers } : {}
3016
5012
  }
@@ -3026,6 +5022,9 @@ var OllamaClient = class {
3026
5022
  this.middleware = config.middleware;
3027
5023
  this.onLifecycleEvent = config.onLifecycleEvent;
3028
5024
  this.retryConfig = typeof config.retries === "number" ? { ...DEFAULT_RETRY_CONFIG, maxRetries: config.retries } : { ...DEFAULT_RETRY_CONFIG, ...config.retries };
5025
+ this.defaultContextLength = config.defaultContextLength;
5026
+ this.contextWarningThreshold = config.contextWarningThreshold;
5027
+ this.onContextOverflow = config.onContextOverflow ?? "warn";
3029
5028
  this.models = new ModelsClient((op, opts) => this.executeWithFailover(op, opts));
3030
5029
  }
3031
5030
  get modelsClient() {
@@ -3039,42 +5038,130 @@ var OllamaClient = class {
3039
5038
  * API with the generated `NativeApi` / `OpenAIApi` / `AnthropicApi`
3040
5039
  * surface without configuring two separate HttpClient instances.
3041
5040
  *
3042
- * The returned runtime is bound to the registry's first candidate
3043
- * endpoint (or the resolved single-endpoint config if no `endpoints`
3044
- * array was provided). For multi-endpoint configs that need per-call
3045
- * routing, prefer constructing `OllamaRuntime` directly with a
3046
- * specific HttpClient:
3047
- *
3048
- * ```ts
3049
- * const http = new HttpClient({ baseUrl: 'http://host-a:11434' });
3050
- * const runtime = new OllamaRuntime({ http });
3051
- * const api = new NativeApi(runtime);
3052
- * ```
5041
+ * Wave 14: the runtime now participates in multi-endpoint failover.
5042
+ * Each request is routed through this client's `executeWithFailover`
5043
+ * machinery via a {@link FailoverHttpClient} wrapper. If the first
5044
+ * candidate endpoint is down, requests automatically fail over to
5045
+ * the next healthy candidate — matching the behavior of the
5046
+ * hand-written OllamaClient methods (chat, generate, etc.).
3053
5047
  *
3054
5048
  * The runtime is cached on first call — subsequent calls return the
3055
- * same instance.
5049
+ * same instance. The `FailoverHttpClient` holds a reference to this
5050
+ * client, so it always sees the current endpoint registry state.
3056
5051
  */
3057
5052
  get runtime() {
3058
5053
  if (this._runtime === void 0) {
3059
5054
  const candidates = this.registry.candidates();
3060
5055
  const endpoint = candidates[0];
3061
5056
  const baseUrl = endpoint?.baseUrl ?? "http://localhost:11434";
3062
- const http = new HttpClient({
3063
- baseUrl,
3064
- ...endpoint?.apiKey !== void 0 ? { apiKey: endpoint.apiKey } : {},
3065
- ...endpoint?.headers !== void 0 ? { headers: endpoint.headers } : {},
5057
+ const http = new FailoverHttpClient(this, baseUrl);
5058
+ const cloudHttp = new HttpClient({
5059
+ baseUrl: OLLAMA_CLOUD_BASE_URL,
5060
+ ...this.cloudApiKey !== void 0 ? { apiKey: this.cloudApiKey } : {},
3066
5061
  fetch: this.fetchImpl,
3067
5062
  ...this.middleware !== void 0 ? { middleware: this.middleware } : {},
3068
5063
  ...this.onLifecycleEvent !== void 0 ? { onLifecycleEvent: this.onLifecycleEvent } : {}
3069
5064
  });
3070
5065
  this._runtime = new OllamaRuntime({
3071
5066
  http,
5067
+ cloudHttp,
3072
5068
  localMode: inferRuntimeMode(baseUrl) === "local"
3073
5069
  });
3074
5070
  }
3075
5071
  return this._runtime;
3076
5072
  }
3077
5073
  _runtime;
5074
+ /**
5075
+ * Lazily-constructed `NativeApi` bound to this client's runtime. Exposes
5076
+ * the full generated native API surface (chat, generate, embed, systemOne,
5077
+ * etc.) with contract-driven types and runtime enforcement.
5078
+ *
5079
+ * Wave 13: `systemOne()` delegates to this accessor.
5080
+ */
5081
+ get native() {
5082
+ if (this._nativeApi === void 0) {
5083
+ this._nativeApi = new NativeApi(this.runtime);
5084
+ }
5085
+ return this._nativeApi;
5086
+ }
5087
+ _nativeApi;
5088
+ /**
5089
+ * Higher-level System One decision helpers (Wave 14B).
5090
+ *
5091
+ * Ergonomic wrappers around `systemOne()` for the six most common
5092
+ * decision patterns: `choice()`, `noul()`, `score()`, `route()`,
5093
+ * `verify()`, `rank()`. Each constructs a single-question request
5094
+ * and extracts the typed answer.
5095
+ *
5096
+ * ```ts
5097
+ * const result = await client.decision.choice({
5098
+ * model: 'tev1:4b',
5099
+ * state: 'Customer was charged twice',
5100
+ * instructions: 'What is the primary intent?',
5101
+ * criteria: {
5102
+ * refund: 'Customer wants a refund',
5103
+ * duplicate_charge: 'Customer reports multiple charges',
5104
+ * },
5105
+ * });
5106
+ * console.log(result.choice); // 'duplicate_charge'
5107
+ * console.log(result.confidence); // 0.89
5108
+ * ```
5109
+ */
5110
+ get decision() {
5111
+ if (this._decision === void 0) {
5112
+ this._decision = createDecision(this);
5113
+ }
5114
+ return this._decision;
5115
+ }
5116
+ _decision;
5117
+ /**
5118
+ * Ergonomic OpenAI Responses API bridge (`client.responses.create()` /
5119
+ * `.stream()`). Dual-mode transport: prefers the native `POST
5120
+ * /v1/responses` endpoint (Ollama ≥ v0.13.3) and transparently re-issues via
5121
+ * `/api/chat` when the server answers 404, so migrating OpenAI code works
5122
+ * unchanged against older servers. See `src/responses.ts`.
5123
+ */
5124
+ get responses() {
5125
+ if (this._responses === void 0) {
5126
+ this._responses = new ResponsesModule((op, opts) => this.executeWithFailover(op, opts));
5127
+ }
5128
+ return this._responses;
5129
+ }
5130
+ _responses;
5131
+ session(modelOrOptions, systemPrompt) {
5132
+ const options = typeof modelOrOptions === "string" ? {
5133
+ model: modelOrOptions,
5134
+ ...systemPrompt !== void 0 ? { systemPrompt } : {}
5135
+ } : modelOrOptions;
5136
+ return new ConversationSession(this, options);
5137
+ }
5138
+ /**
5139
+ * Context-window pre-flight enforcement — see `src/context-safety.ts` and
5140
+ * the `defaultContextLength` / `onContextOverflow` config docs. Logs a
5141
+ * warning (default) or throws client-side when the estimated prompt size
5142
+ * is close to / beyond the effective context window.
5143
+ */
5144
+ enforceContextSafety(kind, check2) {
5145
+ if (!check2.exceedsThreshold) return;
5146
+ const message = contextWarningMessage(check2, kind);
5147
+ if (this.onContextOverflow === "throw") {
5148
+ throw new OllamaClientError(message, { code: "context_overflow" });
5149
+ }
5150
+ this.logger.warn(message);
5151
+ }
5152
+ /**
5153
+ * Returns `options` with `num_ctx` injected from the client's
5154
+ * `defaultContextLength` when the request didn't set one — making the
5155
+ * effective context window explicit on the wire instead of relying on
5156
+ * Ollama's silently-truncating server default. Returns the original
5157
+ * reference when there's nothing to inject (the common case).
5158
+ */
5159
+ injectDefaultContextLength(options) {
5160
+ if (options?.num_ctx !== void 0 || this.defaultContextLength === void 0) {
5161
+ return options;
5162
+ }
5163
+ return { ...options, num_ctx: this.defaultContextLength };
5164
+ }
3078
5165
  /**
3079
5166
  * Fail-fast guard for `format` (structured output) requests: throws before any network
3080
5167
  * call if the candidate endpoint is inferred as Ollama Cloud, which does not currently
@@ -3184,7 +5271,7 @@ var OllamaClient = class {
3184
5271
  }
3185
5272
  }
3186
5273
  async chat(req) {
3187
- const encodedMessages = await withEncodedMessageImages(req.messages);
5274
+ const encodedMessages = await withEncodedMessageImages(req.messages, req.signal);
3188
5275
  const messages = encodedMessages.map((message) => {
3189
5276
  if (message.role === "tool" && message.tool_call_id !== void 0) {
3190
5277
  const { tool_call_id: _toolCallId, ...nativeMessage } = message;
@@ -3192,6 +5279,14 @@ var OllamaClient = class {
3192
5279
  }
3193
5280
  return message;
3194
5281
  });
5282
+ this.enforceContextSafety(
5283
+ "chat",
5284
+ checkChatContext(req, {
5285
+ defaultContextLength: this.defaultContextLength,
5286
+ threshold: this.contextWarningThreshold
5287
+ })
5288
+ );
5289
+ const options = this.injectDefaultContextLength(req.options);
3195
5290
  if (req.stream) {
3196
5291
  return this.executeWithFailover(
3197
5292
  async (http, signal) => {
@@ -3199,7 +5294,7 @@ var OllamaClient = class {
3199
5294
  this.assertStructuredOutputSupported(http.baseUrl, req.model);
3200
5295
  const stream = await http.requestStream({
3201
5296
  path: "/api/chat",
3202
- body: { ...req, messages, stream: true },
5297
+ body: { ...req, options, messages, stream: true },
3203
5298
  signal
3204
5299
  });
3205
5300
  return normalizeChatStream(stream, signal);
@@ -3228,7 +5323,7 @@ var OllamaClient = class {
3228
5323
  this.assertStructuredOutputSupported(http.baseUrl, req.model);
3229
5324
  return http.request({
3230
5325
  path: "/api/chat",
3231
- body: { ...req, messages, stream: false },
5326
+ body: { ...req, options, messages, stream: false },
3232
5327
  signal
3233
5328
  });
3234
5329
  }, req);
@@ -3255,7 +5350,15 @@ var OllamaClient = class {
3255
5350
  return parseStructuredOutput(res.message.content, schema);
3256
5351
  }
3257
5352
  async generate(req) {
3258
- const encodedReq = await withEncodedImages(req);
5353
+ const encodedReq = await withEncodedImages(req, req.signal);
5354
+ this.enforceContextSafety(
5355
+ "generate",
5356
+ checkGenerateContext(req, {
5357
+ defaultContextLength: this.defaultContextLength,
5358
+ threshold: this.contextWarningThreshold
5359
+ })
5360
+ );
5361
+ const options = this.injectDefaultContextLength(req.options);
3259
5362
  if (encodedReq.stream) {
3260
5363
  return this.executeWithFailover(
3261
5364
  async (http, signal) => {
@@ -3263,7 +5366,7 @@ var OllamaClient = class {
3263
5366
  this.assertStructuredOutputSupported(http.baseUrl, encodedReq.model);
3264
5367
  const stream = await http.requestStream({
3265
5368
  path: "/api/generate",
3266
- body: { ...encodedReq, stream: true },
5369
+ body: { ...encodedReq, options, stream: true },
3267
5370
  signal
3268
5371
  });
3269
5372
  return normalizeGenerateStream(stream, signal);
@@ -3289,7 +5392,7 @@ var OllamaClient = class {
3289
5392
  this.assertStructuredOutputSupported(http.baseUrl, encodedReq.model);
3290
5393
  return http.request({
3291
5394
  path: "/api/generate",
3292
- body: { ...encodedReq, stream: false },
5395
+ body: { ...encodedReq, options, stream: false },
3293
5396
  signal
3294
5397
  });
3295
5398
  }, encodedReq);
@@ -3347,6 +5450,55 @@ var OllamaClient = class {
3347
5450
  version = () => this.models.version();
3348
5451
  createBlob = (digest, data) => this.models.createBlob(digest, data);
3349
5452
  checkBlob = (digest) => this.models.checkBlob(digest);
5453
+ /**
5454
+ * Immediately unloads `model` from VRAM. Convenience alias for
5455
+ * {@link ModelsClient.unload} — see that method for the full
5456
+ * lifecycle contract.
5457
+ */
5458
+ unloadModel = (model) => this.models.unload(model);
5459
+ /**
5460
+ * Pre-loads and pins `model` into VRAM indefinitely. Convenience
5461
+ * alias for {@link ModelsClient.pin} — see that method for the
5462
+ * full lifecycle contract.
5463
+ */
5464
+ pinModel = (model) => this.models.pin(model);
5465
+ // --- System One (Wave 13) ---
5466
+ /**
5467
+ * Ollama's System One decision layer (`POST /v1/systemone`) — evaluates
5468
+ * 1–64 typed questions (choice, noul, or score) against a provided state
5469
+ * and returns typed answers with probabilities and confidence.
5470
+ *
5471
+ * Local-only; requires Ollama >= 0.35.0. The runtime enforces the
5472
+ * version constraint automatically (lazily fetching `/api/version` when
5473
+ * `enforceVersion` is `'auto'` or `'strict'`) and rejects cloud-mode
5474
+ * calls. Request size is checked client-side: 64 KiB without images,
5475
+ * 32 MiB with images.
5476
+ *
5477
+ * The generic `Q` parameter captures the caller's question map at
5478
+ * compile time, giving key-safe answer access:
5479
+ *
5480
+ * ```ts
5481
+ * const result = await ollama.systemOne({
5482
+ * model: 'tev1:4b',
5483
+ * state: { ticket: 'Customer was charged twice' },
5484
+ * questions: {
5485
+ * intent: { type: 'choice', instructions: '...', criteria: { ... } },
5486
+ * urgent: { type: 'noul', instructions: '...' },
5487
+ * },
5488
+ * });
5489
+ * result.answers.intent // ✓ SystemOneAnswer (choice)
5490
+ * result.answers.urgent // ✓ SystemOneAnswer (noul)
5491
+ * result.answers.typo // ✗ TypeScript error
5492
+ * ```
5493
+ *
5494
+ * For the low-level generated API (without the generic wrapper), use
5495
+ * `client.native.systemOne(request)` directly.
5496
+ */
5497
+ async systemOne(request) {
5498
+ return this.native.systemOne(
5499
+ request
5500
+ );
5501
+ }
3350
5502
  // --- Web Endpoints ---
3351
5503
  /**
3352
5504
  * Ollama's hosted web search tool (`POST https://ollama.com/api/web_search`) — a fixed
@@ -3379,6 +5531,63 @@ var OllamaClient = class {
3379
5531
  req
3380
5532
  );
3381
5533
  }
5534
+ /**
5535
+ * Ollama Cloud usage statistics (`GET https://ollama.com/api/usage`) — request counts,
5536
+ * USD spend, and token totals (including cached input tokens) for cloud inference,
5537
+ * web search, and web fetch, bucketed by hour (`range: '24h'`) or day (`'7d'`/`'30d'`).
5538
+ *
5539
+ * See {@link OllamaClient.webSearch} for the cloud-endpoint/auth/timeout/retry
5540
+ * behavior, which applies identically here. Query parameters mirror the official
5541
+ * endpoint: `range` (`'24h' | '7d' | '30d'`, server default `'7d'`) and `scope`
5542
+ * (`'self' | 'team'`, server default `'self'`; team scope requires a team admin).
5543
+ * Omitted options are left unset so the server applies its own defaults.
5544
+ *
5545
+ * ```ts
5546
+ * const usage = await client.usage({ range: '24h' });
5547
+ * console.log(usage.totals.request_count, usage.totals.usage_usd);
5548
+ * for (const bucket of usage.buckets) {
5549
+ * if (bucket.partial) continue; // current hour, still in progress
5550
+ * // ...
5551
+ * }
5552
+ * ```
5553
+ */
5554
+ usage(req = {}) {
5555
+ const params = new URLSearchParams();
5556
+ if (req.range !== void 0) params.set("range", req.range);
5557
+ if (req.scope !== void 0) params.set("scope", req.scope);
5558
+ const query = params.toString();
5559
+ return this.executeCloudRequest(
5560
+ (http, signal) => http.request({
5561
+ path: query ? `/api/usage?${query}` : "/api/usage",
5562
+ method: "GET",
5563
+ signal
5564
+ }),
5565
+ req
5566
+ );
5567
+ }
5568
+ /**
5569
+ * Ollama Cloud balance (`GET https://ollama.com/api/balance`) — remaining included
5570
+ * and purchased usage credits. The included balance is either a plan-period credit
5571
+ * object (`balance_usd`/`allowance_usd`/`period`) or, on legacy plans, session/weekly
5572
+ * percentage limits; the shape is discriminated at runtime by the server response.
5573
+ * See {@link OllamaClient.webSearch} for the cloud-endpoint/auth/timeout/retry
5574
+ * behavior, which applies identically here.
5575
+ *
5576
+ * ```ts
5577
+ * const balance = await client.balance();
5578
+ * if ('balance_usd' in balance.included) {
5579
+ * console.log(balance.included.balance_usd, balance.purchased.balance_usd);
5580
+ * } else {
5581
+ * console.log(balance.included.session.remaining_percent, '% of session limit left');
5582
+ * }
5583
+ * ```
5584
+ */
5585
+ balance(req = {}) {
5586
+ return this.executeCloudRequest(
5587
+ (http, signal) => http.request({ path: "/api/balance", method: "GET", signal }),
5588
+ req
5589
+ );
5590
+ }
3382
5591
  /**
3383
5592
  * Runs `operation` against the fixed Ollama Cloud host with this client's default
3384
5593
  * timeout and retry policy — the same primitives `executeWithFailover` uses, minus the
@@ -3482,6 +5691,91 @@ var OllamaClient = class {
3482
5691
  }
3483
5692
  };
3484
5693
 
5694
+ // src/transport/fetch-with-backoff.ts
5695
+ var RETRYABLE_STATUS_CODES = /* @__PURE__ */ new Set([429, 503]);
5696
+ var DEFAULT_FETCH_BACKOFF_CONFIG = {
5697
+ maxRetries: 3
5698
+ };
5699
+ function sleep2(ms, signal) {
5700
+ return new Promise((resolve, reject) => {
5701
+ if (signal?.aborted) {
5702
+ reject(
5703
+ Object.assign(new Error("fetchWithBackoff aborted during backoff sleep"), {
5704
+ name: "AbortError"
5705
+ })
5706
+ );
5707
+ return;
5708
+ }
5709
+ const timer = setTimeout(() => {
5710
+ signal?.removeEventListener("abort", onAbort);
5711
+ resolve();
5712
+ }, ms);
5713
+ const onAbort = () => {
5714
+ clearTimeout(timer);
5715
+ signal?.removeEventListener("abort", onAbort);
5716
+ reject(
5717
+ Object.assign(new Error("fetchWithBackoff aborted during backoff sleep"), {
5718
+ name: "AbortError"
5719
+ })
5720
+ );
5721
+ };
5722
+ signal?.addEventListener("abort", onAbort, { once: true });
5723
+ });
5724
+ }
5725
+ async function fetchWithBackoff(input, init, config = {}) {
5726
+ const fetchImpl = config.fetch ?? globalThis.fetch;
5727
+ if (typeof fetchImpl !== "function") {
5728
+ throw new Error(
5729
+ "fetchWithBackoff: no global fetch is available. Pass a fetch implementation via config.fetch, or run in Node.js >= 18 / a browser-like environment."
5730
+ );
5731
+ }
5732
+ const maxRetries = config.maxRetries ?? DEFAULT_FETCH_BACKOFF_CONFIG.maxRetries;
5733
+ const backoff = config.backoff ?? DEFAULT_BACKOFF;
5734
+ const signal = config.signal;
5735
+ let lastError;
5736
+ for (let attempt = 0; attempt <= maxRetries; attempt += 1) {
5737
+ if (signal?.aborted) {
5738
+ throw Object.assign(new Error("fetchWithBackoff aborted"), { name: "AbortError" });
5739
+ }
5740
+ try {
5741
+ const response = await fetchImpl(input, {
5742
+ ...init,
5743
+ ...signal !== void 0 ? { signal } : {}
5744
+ });
5745
+ if (RETRYABLE_STATUS_CODES.has(response.status) && attempt < maxRetries) {
5746
+ const delayMs = calculateBackoff(attempt, backoff);
5747
+ config.onRetry?.(attempt, response.status, delayMs);
5748
+ await sleep2(delayMs, signal);
5749
+ continue;
5750
+ }
5751
+ return response;
5752
+ } catch (err) {
5753
+ lastError = err;
5754
+ if (signal?.aborted || err instanceof Error && err.name === "AbortError") {
5755
+ throw err;
5756
+ }
5757
+ if (attempt < maxRetries) {
5758
+ const delayMs = calculateBackoff(attempt, backoff);
5759
+ config.onRetry?.(attempt, 0, delayMs);
5760
+ await sleep2(delayMs, signal);
5761
+ continue;
5762
+ }
5763
+ }
5764
+ }
5765
+ throw lastError ?? new Error("fetchWithBackoff: exhausted retry attempts");
5766
+ }
5767
+
5768
+ // src/done-reason.ts
5769
+ var KNOWN_DONE_REASONS = /* @__PURE__ */ new Set([
5770
+ "stop",
5771
+ "length",
5772
+ "load",
5773
+ "unload"
5774
+ ]);
5775
+ function isKnownDoneReason(value) {
5776
+ return value !== void 0 && value !== null && KNOWN_DONE_REASONS.has(value);
5777
+ }
5778
+
3485
5779
  // src/streaming/adapters.ts
3486
5780
  function toTextStream(stream) {
3487
5781
  const iterator = stream[Symbol.asyncIterator]();
@@ -3937,9 +6231,9 @@ function validate(value, schema, path) {
3937
6231
  ["exclusiveMinimum", (n, b) => n > b, "must be greater than"],
3938
6232
  ["exclusiveMaximum", (n, b) => n < b, "must be less than"]
3939
6233
  ];
3940
- for (const [keyword, check, phrase] of numericRules) {
6234
+ for (const [keyword, check2, phrase] of numericRules) {
3941
6235
  const bound = s[keyword];
3942
- if (typeof bound === "number" && !check(value, bound)) issues.push({ path, message: label(path) + " " + phrase + " " + bound });
6236
+ if (typeof bound === "number" && !check2(value, bound)) issues.push({ path, message: label(path) + " " + phrase + " " + bound });
3943
6237
  }
3944
6238
  const multipleOf = s["multipleOf"];
3945
6239
  if (typeof multipleOf === "number" && multipleOf > 0) {
@@ -4200,8 +6494,6 @@ var McpBridge = class _McpBridge {
4200
6494
  this.client = client;
4201
6495
  this.options = options;
4202
6496
  }
4203
- client;
4204
- options;
4205
6497
  static toOllamaTools(tools, options = {}) {
4206
6498
  const prefix = options.namePrefix ?? "";
4207
6499
  return tools.map((tool) => ({
@@ -4498,6 +6790,13 @@ var Agent = class {
4498
6790
  }
4499
6791
  };
4500
6792
 
6793
+ // src/system-one.ts
6794
+ var MAX_SYSTEM_ONE_IMAGES_BYTES = 32 * 1024 * 1024;
6795
+ var MAX_SYSTEM_ONE_REQUEST_BYTES = 64 * 1024;
6796
+ function estimateSystemOneRequestBytes(request) {
6797
+ return new TextEncoder().encode(JSON.stringify(request)).byteLength;
6798
+ }
6799
+
4501
6800
  exports.ATTR_GEN_AI_OPERATION_NAME = ATTR_GEN_AI_OPERATION_NAME;
4502
6801
  exports.ATTR_GEN_AI_REQUEST_MODEL = ATTR_GEN_AI_REQUEST_MODEL;
4503
6802
  exports.ATTR_GEN_AI_RESPONSE_MODEL = ATTR_GEN_AI_RESPONSE_MODEL;
@@ -4518,24 +6817,35 @@ exports.ATTR_URL_FULL = ATTR_URL_FULL;
4518
6817
  exports.Agent = Agent;
4519
6818
  exports.AnthropicCompatClient = AnthropicCompatClient;
4520
6819
  exports.AnthropicMessagesStream = AnthropicMessagesStream;
6820
+ exports.ConversationSession = ConversationSession;
4521
6821
  exports.DEFAULT_BACKOFF = DEFAULT_BACKOFF;
4522
6822
  exports.DEFAULT_BASE_URL = DEFAULT_BASE_URL;
6823
+ exports.DEFAULT_CONTEXT_WARNING_THRESHOLD = DEFAULT_CONTEXT_WARNING_THRESHOLD;
4523
6824
  exports.DEFAULT_FAILOVER_CODES = DEFAULT_FAILOVER_CODES;
6825
+ exports.DEFAULT_FETCH_BACKOFF_CONFIG = DEFAULT_FETCH_BACKOFF_CONFIG;
4524
6826
  exports.DEFAULT_RETRY_CONFIG = DEFAULT_RETRY_CONFIG;
4525
6827
  exports.DEFAULT_TIMEOUT_MS = DEFAULT_TIMEOUT_MS;
4526
6828
  exports.EndpointRegistry = EndpointRegistry;
4527
6829
  exports.GEN_AI_SYSTEM_OLLAMA = GEN_AI_SYSTEM_OLLAMA;
4528
6830
  exports.HttpClient = HttpClient;
6831
+ exports.IMAGE_TOKEN_ESTIMATE = IMAGE_TOKEN_ESTIMATE;
6832
+ exports.KEEP_ALIVE_INDEFINITE = KEEP_ALIVE_INDEFINITE;
6833
+ exports.KEEP_ALIVE_UNLOAD = KEEP_ALIVE_UNLOAD;
6834
+ exports.KNOWN_DONE_REASONS = KNOWN_DONE_REASONS;
6835
+ exports.MAX_SYSTEM_ONE_IMAGES_BYTES = MAX_SYSTEM_ONE_IMAGES_BYTES;
6836
+ exports.MAX_SYSTEM_ONE_REQUEST_BYTES = MAX_SYSTEM_ONE_REQUEST_BYTES;
4529
6837
  exports.McpBridge = McpBridge;
4530
6838
  exports.ModelsClient = ModelsClient;
4531
6839
  exports.NANOS_PER_MS = NANOS_PER_MS;
4532
6840
  exports.NANOS_PER_SECOND = NANOS_PER_SECOND;
4533
6841
  exports.NOOP_LOGGER = NOOP_LOGGER;
4534
6842
  exports.OLLAMA_CLOUD_BASE_URL = OLLAMA_CLOUD_BASE_URL;
6843
+ exports.OLLAMA_FALLBACK_CONTEXT_LENGTH = OLLAMA_FALLBACK_CONTEXT_LENGTH;
4535
6844
  exports.OllamaAbortError = OllamaAbortError;
4536
6845
  exports.OllamaAgentMaxIterationsError = OllamaAgentMaxIterationsError;
4537
6846
  exports.OllamaAgentMaxToolCallsError = OllamaAgentMaxToolCallsError;
4538
6847
  exports.OllamaAuthError = OllamaAuthError;
6848
+ exports.OllamaBadGatewayError = OllamaBadGatewayError;
4539
6849
  exports.OllamaClient = OllamaClient;
4540
6850
  exports.OllamaClientError = OllamaClientError;
4541
6851
  exports.OllamaGenericClientError = OllamaGenericClientError;
@@ -4544,13 +6854,18 @@ exports.OllamaMcpError = OllamaMcpError;
4544
6854
  exports.OllamaModelRoutingError = OllamaModelRoutingError;
4545
6855
  exports.OllamaNetworkError = OllamaNetworkError;
4546
6856
  exports.OllamaNotFoundError = OllamaNotFoundError;
6857
+ exports.OllamaOverloadedError = OllamaOverloadedError;
4547
6858
  exports.OllamaQuotaExceededError = OllamaQuotaExceededError;
4548
6859
  exports.OllamaRateLimitError = OllamaRateLimitError;
6860
+ exports.OllamaRequestTooLargeError = OllamaRequestTooLargeError;
4549
6861
  exports.OllamaRequestValidationError = OllamaRequestValidationError;
6862
+ exports.OllamaResponseValidationError = OllamaResponseValidationError;
4550
6863
  exports.OllamaServerError = OllamaServerError;
6864
+ exports.OllamaServerVersionUnknownError = OllamaServerVersionUnknownError;
4551
6865
  exports.OllamaSkillInvalidError = OllamaSkillInvalidError;
4552
6866
  exports.OllamaSkillNotFoundError = OllamaSkillNotFoundError;
4553
6867
  exports.OllamaStream = OllamaStream;
6868
+ exports.OllamaStreamError = OllamaStreamError;
4554
6869
  exports.OllamaTimeoutError = OllamaTimeoutError;
4555
6870
  exports.OllamaToolTimeoutError = OllamaToolTimeoutError;
4556
6871
  exports.OllamaToolValidationError = OllamaToolValidationError;
@@ -4561,26 +6876,46 @@ exports.OpenAICompletionStream = OpenAICompletionStream;
4561
6876
  exports.OpenAIResponsesStream = OpenAIResponsesStream;
4562
6877
  exports.OpenAIResponsesStreamError = OpenAIResponsesStreamError;
4563
6878
  exports.QuotaManager = QuotaManager;
6879
+ exports.RETRYABLE_STATUS_CODES = RETRYABLE_STATUS_CODES;
6880
+ exports.ResponsesModule = ResponsesModule;
6881
+ exports.ToolCallAccumulator = ToolCallAccumulator;
4564
6882
  exports.ToolRegistry = ToolRegistry;
4565
6883
  exports.addSpanEvent = addSpanEvent;
4566
6884
  exports.applySkill = applySkill;
4567
6885
  exports.calculateBackoff = calculateBackoff;
6886
+ exports.checkChatContext = checkChatContext;
4568
6887
  exports.checkEndpointHealth = checkEndpointHealth;
6888
+ exports.checkGenerateContext = checkGenerateContext;
4569
6889
  exports.composeMiddleware = composeMiddleware;
6890
+ exports.contextWarningMessage = contextWarningMessage;
4570
6891
  exports.createConsoleLogger = createConsoleLogger;
6892
+ exports.createDecision = createDecision;
4571
6893
  exports.createOllamaCloudFreeTierQuota = createOllamaCloudFreeTierQuota;
4572
6894
  exports.createTimeoutSignal = createTimeoutSignal;
4573
6895
  exports.defineTool = defineTool;
4574
6896
  exports.detectModelCapabilities = detectModelCapabilities;
4575
6897
  exports.encodeImage = encodeImage;
6898
+ exports.estimateChatRequestTokens = estimateChatRequestTokens;
6899
+ exports.estimateGenerateRequestTokens = estimateGenerateRequestTokens;
6900
+ exports.estimateSystemOneRequestBytes = estimateSystemOneRequestBytes;
6901
+ exports.estimateTokens = estimateTokens;
4576
6902
  exports.extractUsage = extractUsage;
6903
+ exports.fetchWithBackoff = fetchWithBackoff;
6904
+ exports.imageStringNeedsResolution = imageStringNeedsResolution;
4577
6905
  exports.inferRuntimeMode = inferRuntimeMode;
6906
+ exports.isKeepAliveSugar = isKeepAliveSugar;
6907
+ exports.isKnownDoneReason = isKnownDoneReason;
6908
+ exports.isSameToolCall = isSameToolCall;
4578
6909
  exports.listAllMcpTools = listAllMcpTools;
4579
6910
  exports.listAvailableModels = listAvailableModels;
4580
6911
  exports.loadMcpTools = loadMcpTools;
4581
6912
  exports.mapError = mapError;
6913
+ exports.mergeToolCall = mergeToolCall;
6914
+ exports.mergeToolCallArgumentsString = mergeToolCallArgumentsString;
6915
+ exports.mergeToolCallArrays = mergeToolCallArrays;
4582
6916
  exports.normalizeChatStream = normalizeChatStream;
4583
6917
  exports.normalizeGenerateStream = normalizeGenerateStream;
6918
+ exports.normalizeKeepAlive = normalizeKeepAlive;
4584
6919
  exports.normalizeProgressStream = normalizeProgressStream;
4585
6920
  exports.parseFrontmatter = parseFrontmatter;
4586
6921
  exports.parseNdjsonStream = parseNdjsonStream;
@@ -4588,6 +6923,7 @@ exports.parseSseStream = parseSseStream;
4588
6923
  exports.parseStructuredOutput = parseStructuredOutput;
4589
6924
  exports.registerMcpTools = registerMcpTools;
4590
6925
  exports.resolveCredentialEndpoints = resolveCredentialEndpoints;
6926
+ exports.resolveImageInput = resolveImageInput;
4591
6927
  exports.setSpanError = setSpanError;
4592
6928
  exports.toDataStream = toDataStream;
4593
6929
  exports.toResponse = toResponse;