@nemesis-oss/ollama-sdk 1.10.0 → 1.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +72 -0
- package/README.md +296 -1
- package/dist/{anthropic-CPaoxeLX.d.ts → anthropic-CKp5TmAP.d.ts} +1 -1
- package/dist/{anthropic-DuE8M6Tz.d.cts → anthropic-Did0lQxP.d.cts} +1 -1
- package/dist/{compose-BQYXdpar.d.ts → compose-6UOPEknN.d.ts} +1 -1
- package/dist/{compose-lt-Qz3lI.d.cts → compose-DhvoUrWa.d.cts} +1 -1
- package/dist/generated-api.d.cts +5 -5
- package/dist/generated-api.d.ts +5 -5
- package/dist/generated-runtime.cjs.map +1 -1
- package/dist/generated-runtime.d.cts +2 -2
- package/dist/generated-runtime.d.ts +2 -2
- package/dist/generated-runtime.js.map +1 -1
- package/dist/index.cjs +790 -106
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +870 -97
- package/dist/index.d.ts +870 -97
- package/dist/index.js +776 -107
- package/dist/index.js.map +1 -1
- package/dist/mcp-generated.d.cts +2 -2
- package/dist/mcp-generated.d.ts +2 -2
- package/dist/{runtime-BV9SK_cf.d.ts → runtime-CIQYCRL3.d.ts} +29 -2
- package/dist/{runtime-BsdBbuOv.d.cts → runtime-DmI1cOEq.d.cts} +29 -2
- package/dist/skills.cjs.map +1 -1
- package/dist/skills.d.cts +3 -3
- package/dist/skills.d.ts +3 -3
- package/dist/skills.js.map +1 -1
- package/dist/{types-BtQexFpT.d.cts → types-B57ZZfp3.d.cts} +15 -1
- package/dist/{types-BtQexFpT.d.ts → types-B57ZZfp3.d.ts} +15 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -341,6 +341,23 @@ var OllamaAgentMaxIterationsError = class extends OllamaClientError {
|
|
|
341
341
|
this.maxIterations = options.maxIterations;
|
|
342
342
|
}
|
|
343
343
|
};
|
|
344
|
+
var OllamaAgentToolLoopError = class extends OllamaClientError {
|
|
345
|
+
/** Name of the tool whose identical call would exceed the repeat budget. */
|
|
346
|
+
toolName;
|
|
347
|
+
/** How many times this exact call would have executed including the rejected one. */
|
|
348
|
+
repeatedExecutions;
|
|
349
|
+
/** The configured per-signature execution budget. */
|
|
350
|
+
maxRepeatedToolCalls;
|
|
351
|
+
/** Canonical `name(args)` signature of the repeated call (very large payloads truncated). */
|
|
352
|
+
signature;
|
|
353
|
+
constructor(message, options) {
|
|
354
|
+
super(message, { ...options, code: "agent_tool_loop_detected", retryable: false });
|
|
355
|
+
this.toolName = options.toolName;
|
|
356
|
+
this.repeatedExecutions = options.repeatedExecutions;
|
|
357
|
+
this.maxRepeatedToolCalls = options.maxRepeatedToolCalls;
|
|
358
|
+
this.signature = options.signature;
|
|
359
|
+
}
|
|
360
|
+
};
|
|
344
361
|
var OllamaMcpError = class extends OllamaClientError {
|
|
345
362
|
mcpMethod;
|
|
346
363
|
toolName;
|
|
@@ -640,6 +657,15 @@ var EndpointRegistry = class {
|
|
|
640
657
|
}
|
|
641
658
|
};
|
|
642
659
|
|
|
660
|
+
// src/transport/url.ts
|
|
661
|
+
function joinUrlPath(baseUrl, path) {
|
|
662
|
+
const base = baseUrl.replace(/\/+$/, "");
|
|
663
|
+
const tail = path.replace(/^\/+/, "");
|
|
664
|
+
if (tail === "") return base;
|
|
665
|
+
if (base === "") return `/${tail}`;
|
|
666
|
+
return `${base}/${tail}`;
|
|
667
|
+
}
|
|
668
|
+
|
|
643
669
|
// src/middleware.ts
|
|
644
670
|
function composeMiddleware(middlewares, finalHandler) {
|
|
645
671
|
return function runPipeline(initialRequest) {
|
|
@@ -1011,6 +1037,27 @@ var ATTR_OLLAMA_AGENT_MAX_ITERATIONS = "ollama.agent.max_iterations";
|
|
|
1011
1037
|
var ATTR_OLLAMA_AGENT_ITERATION = "ollama.agent.iteration";
|
|
1012
1038
|
var GEN_AI_SYSTEM_OLLAMA = "ollama";
|
|
1013
1039
|
|
|
1040
|
+
// src/telemetry/metrics.ts
|
|
1041
|
+
function nsToMs(ns) {
|
|
1042
|
+
return ns ? Math.round(ns / 1e6 * 100) / 100 : 0;
|
|
1043
|
+
}
|
|
1044
|
+
function formatTelemetry(raw) {
|
|
1045
|
+
const evalDurationSec = raw.eval_duration ? raw.eval_duration / 1e9 : 0;
|
|
1046
|
+
const promptDurationSec = raw.prompt_eval_duration ? raw.prompt_eval_duration / 1e9 : 0;
|
|
1047
|
+
const promptEvaluated = raw.prompt_eval_count ?? 0;
|
|
1048
|
+
const promptCached = raw.prompt_eval_cached_count ?? 0;
|
|
1049
|
+
const promptTotal = promptCached + promptEvaluated;
|
|
1050
|
+
return {
|
|
1051
|
+
totalLatencyMs: nsToMs(raw.total_duration),
|
|
1052
|
+
modelLoadMs: nsToMs(raw.load_duration),
|
|
1053
|
+
promptEvalMs: nsToMs(raw.prompt_eval_duration),
|
|
1054
|
+
generationMs: nsToMs(raw.eval_duration),
|
|
1055
|
+
tokensPerSecond: evalDurationSec > 0 && raw.eval_count ? Math.round(raw.eval_count / evalDurationSec * 10) / 10 : 0,
|
|
1056
|
+
promptTokensPerSecond: promptDurationSec > 0 && promptEvaluated ? Math.round(promptEvaluated / promptDurationSec * 10) / 10 : 0,
|
|
1057
|
+
cacheHitRatio: promptTotal > 0 ? Math.round(promptCached / promptTotal * 1e3) / 1e3 : 0
|
|
1058
|
+
};
|
|
1059
|
+
}
|
|
1060
|
+
|
|
1014
1061
|
// src/transport/http.ts
|
|
1015
1062
|
function routeTemplate(path) {
|
|
1016
1063
|
return path.replace(/^\/api\/blobs\/.+$/, "/api/blobs/{digest}");
|
|
@@ -1192,7 +1239,7 @@ var HttpClient = class {
|
|
|
1192
1239
|
}
|
|
1193
1240
|
}
|
|
1194
1241
|
async request(options) {
|
|
1195
|
-
const url =
|
|
1242
|
+
const url = joinUrlPath(this.baseUrl, options.path);
|
|
1196
1243
|
const method = options.method ?? (options.body !== void 0 || options.rawBody !== void 0 ? "POST" : "GET");
|
|
1197
1244
|
const headers = this.buildHeaders(options.headers);
|
|
1198
1245
|
if (options.rawBody !== void 0 && !options.headers?.["Content-Type"]) {
|
|
@@ -1252,7 +1299,7 @@ var HttpClient = class {
|
|
|
1252
1299
|
* can decode their provider-specific event payloads without duplicating transport logic.
|
|
1253
1300
|
*/
|
|
1254
1301
|
async requestSseStream(options) {
|
|
1255
|
-
const url =
|
|
1302
|
+
const url = joinUrlPath(this.baseUrl, options.path);
|
|
1256
1303
|
const method = options.method ?? "POST";
|
|
1257
1304
|
const headers = {
|
|
1258
1305
|
...this.buildHeaders(options.headers),
|
|
@@ -1319,7 +1366,7 @@ var HttpClient = class {
|
|
|
1319
1366
|
);
|
|
1320
1367
|
}
|
|
1321
1368
|
async requestStream(options) {
|
|
1322
|
-
const url =
|
|
1369
|
+
const url = joinUrlPath(this.baseUrl, options.path);
|
|
1323
1370
|
const method = options.method ?? "POST";
|
|
1324
1371
|
const headers = this.buildHeaders(options.headers);
|
|
1325
1372
|
return withSpan(
|
|
@@ -1548,6 +1595,256 @@ async function listAvailableModels(http) {
|
|
|
1548
1595
|
});
|
|
1549
1596
|
return [...res.models];
|
|
1550
1597
|
}
|
|
1598
|
+
|
|
1599
|
+
// src/context-safety.ts
|
|
1600
|
+
var DEFAULT_CONTEXT_WARNING_THRESHOLD = 0.9;
|
|
1601
|
+
var IMAGE_TOKEN_ESTIMATE = 300;
|
|
1602
|
+
var CHARS_PER_TOKEN = 4;
|
|
1603
|
+
function isCjkCodePoint(code) {
|
|
1604
|
+
return code >= 19968 && code <= 40959 || // CJK Unified Ideographs
|
|
1605
|
+
code >= 13312 && code <= 19903 || // CJK Extension A
|
|
1606
|
+
code >= 12352 && code <= 12543 || // Hiragana + Katakana
|
|
1607
|
+
code >= 44032 && code <= 55215 || // Hangul syllables
|
|
1608
|
+
code >= 63744 && code <= 64255 || // CJK Compatibility Ideographs
|
|
1609
|
+
code >= 65280 && code <= 65519;
|
|
1610
|
+
}
|
|
1611
|
+
function estimateTokens(text) {
|
|
1612
|
+
if (text.length === 0) return 0;
|
|
1613
|
+
let cjk = 0;
|
|
1614
|
+
let other = 0;
|
|
1615
|
+
for (const char of text) {
|
|
1616
|
+
if (isCjkCodePoint(char.codePointAt(0) ?? 0)) cjk += 1;
|
|
1617
|
+
else other += 1;
|
|
1618
|
+
}
|
|
1619
|
+
return Math.ceil(cjk + other / CHARS_PER_TOKEN);
|
|
1620
|
+
}
|
|
1621
|
+
var MESSAGE_OVERHEAD_TOKENS = 4;
|
|
1622
|
+
function estimateMessageTokens(message) {
|
|
1623
|
+
let total = MESSAGE_OVERHEAD_TOKENS + estimateTokens(message.content ?? "");
|
|
1624
|
+
if (message.thinking !== void 0) total += estimateTokens(message.thinking);
|
|
1625
|
+
if (message.tool_calls !== void 0) {
|
|
1626
|
+
for (const call of message.tool_calls) {
|
|
1627
|
+
total += estimateTokens(call.function.name);
|
|
1628
|
+
total += estimateTokens(JSON.stringify(call.function.arguments ?? {}));
|
|
1629
|
+
}
|
|
1630
|
+
}
|
|
1631
|
+
if (message.images !== void 0) total += message.images.length * IMAGE_TOKEN_ESTIMATE;
|
|
1632
|
+
return total;
|
|
1633
|
+
}
|
|
1634
|
+
function estimateChatRequestTokens(req) {
|
|
1635
|
+
let total = 0;
|
|
1636
|
+
for (const message of req.messages) total += estimateMessageTokens(message);
|
|
1637
|
+
if (req.tools !== void 0) {
|
|
1638
|
+
for (const tool of req.tools) {
|
|
1639
|
+
total += estimateTokens(tool.function.description ?? "");
|
|
1640
|
+
total += estimateTokens(JSON.stringify(tool.function.parameters ?? {}));
|
|
1641
|
+
}
|
|
1642
|
+
}
|
|
1643
|
+
return total;
|
|
1644
|
+
}
|
|
1645
|
+
function estimateGenerateRequestTokens(req) {
|
|
1646
|
+
let total = estimateTokens(req.prompt ?? "");
|
|
1647
|
+
if (req.system !== void 0) total += estimateTokens(req.system);
|
|
1648
|
+
if (req.template !== void 0) total += estimateTokens(req.template);
|
|
1649
|
+
if (req.images !== void 0) total += req.images.length * IMAGE_TOKEN_ESTIMATE;
|
|
1650
|
+
return total;
|
|
1651
|
+
}
|
|
1652
|
+
function checkChatContext(req, options = {}) {
|
|
1653
|
+
return check(estimateChatRequestTokens(req), req.options?.num_ctx, options);
|
|
1654
|
+
}
|
|
1655
|
+
function checkGenerateContext(req, options = {}) {
|
|
1656
|
+
return check(estimateGenerateRequestTokens(req), req.options?.num_ctx, options);
|
|
1657
|
+
}
|
|
1658
|
+
function check(estimatedPromptTokens, requestNumCtx, options) {
|
|
1659
|
+
const threshold = options.threshold ?? DEFAULT_CONTEXT_WARNING_THRESHOLD;
|
|
1660
|
+
if (requestNumCtx !== void 0) {
|
|
1661
|
+
return {
|
|
1662
|
+
estimatedPromptTokens,
|
|
1663
|
+
effectiveContextLength: requestNumCtx,
|
|
1664
|
+
windowSource: "request",
|
|
1665
|
+
exceedsThreshold: estimatedPromptTokens > requestNumCtx * threshold
|
|
1666
|
+
};
|
|
1667
|
+
}
|
|
1668
|
+
if (options.defaultContextLength !== void 0) {
|
|
1669
|
+
return {
|
|
1670
|
+
estimatedPromptTokens,
|
|
1671
|
+
effectiveContextLength: options.defaultContextLength,
|
|
1672
|
+
windowSource: "client-default",
|
|
1673
|
+
exceedsThreshold: estimatedPromptTokens > options.defaultContextLength * threshold
|
|
1674
|
+
};
|
|
1675
|
+
}
|
|
1676
|
+
return {
|
|
1677
|
+
estimatedPromptTokens,
|
|
1678
|
+
effectiveContextLength: OLLAMA_FALLBACK_CONTEXT_LENGTH,
|
|
1679
|
+
windowSource: "server-fallback",
|
|
1680
|
+
exceedsThreshold: estimatedPromptTokens > OLLAMA_FALLBACK_CONTEXT_LENGTH
|
|
1681
|
+
};
|
|
1682
|
+
}
|
|
1683
|
+
var OLLAMA_FALLBACK_CONTEXT_LENGTH = 2048;
|
|
1684
|
+
function contextWarningMessage(check2, kind) {
|
|
1685
|
+
const where = `OllamaClient.${kind}()`;
|
|
1686
|
+
const estimate = `~${check2.estimatedPromptTokens} tokens`;
|
|
1687
|
+
if (check2.windowSource === "request") {
|
|
1688
|
+
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\`.`;
|
|
1689
|
+
}
|
|
1690
|
+
if (check2.windowSource === "client-default") {
|
|
1691
|
+
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\`.`;
|
|
1692
|
+
}
|
|
1693
|
+
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\`.`;
|
|
1694
|
+
}
|
|
1695
|
+
|
|
1696
|
+
// src/context-discovery.ts
|
|
1697
|
+
function extractParameterNumCtx(parameters) {
|
|
1698
|
+
if (!parameters) return void 0;
|
|
1699
|
+
const match = /^[ \t]*num_ctx[ \t]+(\d+)\s*$/m.exec(parameters);
|
|
1700
|
+
if (match === null) return void 0;
|
|
1701
|
+
const value = Number.parseInt(match[1], 10);
|
|
1702
|
+
return Number.isFinite(value) && value > 0 ? value : void 0;
|
|
1703
|
+
}
|
|
1704
|
+
function matchesModelReference(entry, model) {
|
|
1705
|
+
const exact = entry.name === model || entry.model === model;
|
|
1706
|
+
const taglessPrefix = !model.includes(":") && (entry.name !== void 0 && entry.name.startsWith(`${model}:`) || entry.model !== void 0 && entry.model.startsWith(`${model}:`));
|
|
1707
|
+
return exact || taglessPrefix;
|
|
1708
|
+
}
|
|
1709
|
+
function isModelResident(psModels, model) {
|
|
1710
|
+
return psModels.some((entry) => matchesModelReference(entry, model));
|
|
1711
|
+
}
|
|
1712
|
+
function findRunningModelContextLength(psModels, model) {
|
|
1713
|
+
for (const entry of psModels) {
|
|
1714
|
+
if (matchesModelReference(entry, model)) {
|
|
1715
|
+
return typeof entry.context_length === "number" && Number.isFinite(entry.context_length) && entry.context_length > 0 ? entry.context_length : void 0;
|
|
1716
|
+
}
|
|
1717
|
+
}
|
|
1718
|
+
return void 0;
|
|
1719
|
+
}
|
|
1720
|
+
function positiveInt(value) {
|
|
1721
|
+
return typeof value === "number" && Number.isFinite(value) && value > 0 ? value : void 0;
|
|
1722
|
+
}
|
|
1723
|
+
function resolveContextLength(signals) {
|
|
1724
|
+
const running = positiveInt(signals.running);
|
|
1725
|
+
const parameter = positiveInt(signals.parameter);
|
|
1726
|
+
const native = positiveInt(signals.native);
|
|
1727
|
+
if (running !== void 0) {
|
|
1728
|
+
return {
|
|
1729
|
+
contextLength: running,
|
|
1730
|
+
source: "running",
|
|
1731
|
+
...parameter !== void 0 ? { parameterContextLength: parameter } : {},
|
|
1732
|
+
...native !== void 0 ? { nativeContextLength: native } : {},
|
|
1733
|
+
runningContextLength: running
|
|
1734
|
+
};
|
|
1735
|
+
}
|
|
1736
|
+
if (parameter !== void 0) {
|
|
1737
|
+
return {
|
|
1738
|
+
contextLength: parameter,
|
|
1739
|
+
source: "parameters",
|
|
1740
|
+
parameterContextLength: parameter,
|
|
1741
|
+
...native !== void 0 ? { nativeContextLength: native } : {}
|
|
1742
|
+
};
|
|
1743
|
+
}
|
|
1744
|
+
if (native !== void 0) {
|
|
1745
|
+
return { contextLength: native, source: "model-info", nativeContextLength: native };
|
|
1746
|
+
}
|
|
1747
|
+
return { contextLength: OLLAMA_FALLBACK_CONTEXT_LENGTH, source: "fallback" };
|
|
1748
|
+
}
|
|
1749
|
+
|
|
1750
|
+
// src/providers/model-affinity-router.ts
|
|
1751
|
+
var DEFAULT_TTL_MS = 3e4;
|
|
1752
|
+
var DEFAULT_FAILURE_RETRY_MS = 5e3;
|
|
1753
|
+
var ModelAffinityRouter = class {
|
|
1754
|
+
fetchPs;
|
|
1755
|
+
ttlMs;
|
|
1756
|
+
failureRetryMs;
|
|
1757
|
+
now;
|
|
1758
|
+
cache = /* @__PURE__ */ new Map();
|
|
1759
|
+
failedAt = /* @__PURE__ */ new Map();
|
|
1760
|
+
inflight = /* @__PURE__ */ new Map();
|
|
1761
|
+
constructor(fetchPs, options = {}) {
|
|
1762
|
+
this.fetchPs = fetchPs;
|
|
1763
|
+
this.ttlMs = options.ttlMs ?? DEFAULT_TTL_MS;
|
|
1764
|
+
this.failureRetryMs = options.failureRetryMs ?? DEFAULT_FAILURE_RETRY_MS;
|
|
1765
|
+
this.now = options.now ?? Date.now;
|
|
1766
|
+
}
|
|
1767
|
+
/**
|
|
1768
|
+
* Synchronously reorders `candidates` so endpoints whose fresh `/api/ps`
|
|
1769
|
+
* snapshot shows `model` resident come first (relative order preserved within
|
|
1770
|
+
* each group). Returns the input unchanged — and kicks a background refresh —
|
|
1771
|
+
* when any candidate's snapshot is missing or stale: a wrong reorder is worse
|
|
1772
|
+
* than no reorder, and the first request after a cache miss simply isn't
|
|
1773
|
+
* reordered. No-op without a `model` or with fewer than two candidates.
|
|
1774
|
+
*/
|
|
1775
|
+
reorder(candidates, model) {
|
|
1776
|
+
if (model === void 0 || candidates.length < 2) return candidates;
|
|
1777
|
+
const currentTime = this.now();
|
|
1778
|
+
let allFresh = true;
|
|
1779
|
+
for (const endpoint of candidates) {
|
|
1780
|
+
const entry = this.cache.get(endpoint.name);
|
|
1781
|
+
if (entry === void 0 || currentTime - entry.fetchedAt > this.ttlMs) {
|
|
1782
|
+
allFresh = false;
|
|
1783
|
+
this.kickRefresh(endpoint);
|
|
1784
|
+
}
|
|
1785
|
+
}
|
|
1786
|
+
if (!allFresh) return candidates;
|
|
1787
|
+
const resident = candidates.filter((ep) => {
|
|
1788
|
+
const entry = this.cache.get(ep.name);
|
|
1789
|
+
return entry !== void 0 && isModelResident(entry.models, model);
|
|
1790
|
+
});
|
|
1791
|
+
if (resident.length === 0 || resident.length === candidates.length) return candidates;
|
|
1792
|
+
const residentSet = new Set(resident.map((ep) => ep.name));
|
|
1793
|
+
return [...resident, ...candidates.filter((ep) => !residentSet.has(ep.name))];
|
|
1794
|
+
}
|
|
1795
|
+
/**
|
|
1796
|
+
* Forces a refresh of the affinity cache for the given endpoints (deduped with
|
|
1797
|
+
* any in-flight refresh; bypasses the failure backoff, because this is an
|
|
1798
|
+
* explicit caller request — `client.warmModelAffinity()`). Resolves once every
|
|
1799
|
+
* probe has settled — successfully or not; individual failures never reject.
|
|
1800
|
+
*/
|
|
1801
|
+
async warm(endpoints) {
|
|
1802
|
+
await Promise.all(endpoints.map((endpoint) => this.refresh(endpoint)));
|
|
1803
|
+
}
|
|
1804
|
+
/** Introspection snapshots for the given endpoints (defaults to cached ones). */
|
|
1805
|
+
status(endpoints) {
|
|
1806
|
+
const names = endpoints !== void 0 ? endpoints.map((ep) => ep.name) : [...this.cache.keys(), ...this.failedAt.keys()];
|
|
1807
|
+
const byName = new Map((endpoints ?? []).map((ep) => [ep.name, ep]));
|
|
1808
|
+
const currentTime = this.now();
|
|
1809
|
+
return names.filter((name, i, arr) => arr.indexOf(name) === i).map((name) => {
|
|
1810
|
+
const entry = this.cache.get(name);
|
|
1811
|
+
return {
|
|
1812
|
+
endpointName: name,
|
|
1813
|
+
baseUrl: byName.get(name)?.baseUrl ?? "",
|
|
1814
|
+
loadedModels: entry?.models.map((m) => m.name ?? m.model ?? "").filter((n) => n !== "") ?? [],
|
|
1815
|
+
fetchedAt: entry?.fetchedAt,
|
|
1816
|
+
fresh: entry !== void 0 && currentTime - entry.fetchedAt <= this.ttlMs
|
|
1817
|
+
};
|
|
1818
|
+
});
|
|
1819
|
+
}
|
|
1820
|
+
/**
|
|
1821
|
+
* Starts (or joins) a background refresh for `endpoint`, unless one is already
|
|
1822
|
+
* in flight or the failure backoff suppresses it. Fire-and-forget: the promise
|
|
1823
|
+
* is stored only for dedupe/warm-joins; callers never await it on the request
|
|
1824
|
+
* path.
|
|
1825
|
+
*/
|
|
1826
|
+
kickRefresh(endpoint) {
|
|
1827
|
+
if (this.inflight.has(endpoint.name)) return;
|
|
1828
|
+
const lastFailure = this.failedAt.get(endpoint.name);
|
|
1829
|
+
if (lastFailure !== void 0 && this.now() - lastFailure < this.failureRetryMs) return;
|
|
1830
|
+
void this.refresh(endpoint);
|
|
1831
|
+
}
|
|
1832
|
+
/** Refreshes one endpoint's snapshot; never rejects (failures record a backoff). */
|
|
1833
|
+
refresh(endpoint) {
|
|
1834
|
+
const existing = this.inflight.get(endpoint.name);
|
|
1835
|
+
if (existing !== void 0) return existing;
|
|
1836
|
+
const attempt = this.fetchPs(endpoint).then((models) => {
|
|
1837
|
+
this.cache.set(endpoint.name, { models, fetchedAt: this.now() });
|
|
1838
|
+
this.failedAt.delete(endpoint.name);
|
|
1839
|
+
}).catch(() => {
|
|
1840
|
+
this.failedAt.set(endpoint.name, this.now());
|
|
1841
|
+
}).finally(() => {
|
|
1842
|
+
this.inflight.delete(endpoint.name);
|
|
1843
|
+
});
|
|
1844
|
+
this.inflight.set(endpoint.name, attempt);
|
|
1845
|
+
return attempt;
|
|
1846
|
+
}
|
|
1847
|
+
};
|
|
1551
1848
|
function extractJsonSubstring(text) {
|
|
1552
1849
|
const trimmed = text.trim();
|
|
1553
1850
|
if (trimmed.startsWith("{") || trimmed.startsWith("[")) {
|
|
@@ -2273,9 +2570,66 @@ var ModelsClient = class {
|
|
|
2273
2570
|
{ ...request, singleEndpoint: true }
|
|
2274
2571
|
);
|
|
2275
2572
|
}
|
|
2276
|
-
ps() {
|
|
2277
|
-
return this.runner(
|
|
2278
|
-
|
|
2573
|
+
ps(request) {
|
|
2574
|
+
return this.runner(
|
|
2575
|
+
(http, signal) => http.request({ path: "/api/ps", method: "GET", signal }),
|
|
2576
|
+
{
|
|
2577
|
+
singleEndpoint: true,
|
|
2578
|
+
...request?.signal !== void 0 ? { signal: request.signal } : {},
|
|
2579
|
+
...request?.timeoutMs !== void 0 ? { timeoutMs: request.timeoutMs } : {}
|
|
2580
|
+
}
|
|
2581
|
+
);
|
|
2582
|
+
}
|
|
2583
|
+
/**
|
|
2584
|
+
* Discovers the model's real context window from the server — no more
|
|
2585
|
+
* 2048/4096 guesswork. Consults, in precedence order:
|
|
2586
|
+
*
|
|
2587
|
+
* 1. `GET /api/ps` — the window the **running instance actually
|
|
2588
|
+
* allocated** (exact, reflects the Modelfile `num_ctx` default and
|
|
2589
|
+
* what fit in memory); skipped when the model isn't loaded, when
|
|
2590
|
+
* `skipRunningCheck` is set, or harmlessly on servers without the
|
|
2591
|
+
* field.
|
|
2592
|
+
* 2. `POST /api/show` `parameters` — a Modelfile-authored
|
|
2593
|
+
* `num_ctx <n>` default, i.e. the window a cold load will allocate.
|
|
2594
|
+
* 3. `POST /api/show` `model_info["<arch>.context_length"]` — the
|
|
2595
|
+
* model's **native GGUF maximum** (a ceiling to raise `num_ctx`
|
|
2596
|
+
* toward, not the allocated window).
|
|
2597
|
+
* 4. Fallback — Ollama's conservative unset-`num_ctx` default (2048).
|
|
2598
|
+
*
|
|
2599
|
+
* The result carries every signal it found, so `nativeContextLength:
|
|
2600
|
+
* 131072` vs `contextLength: 4096` (a 32× gap seen in the official API
|
|
2601
|
+
* examples) is visible rather than guessed. Typical wiring:
|
|
2602
|
+
*
|
|
2603
|
+
* ```ts
|
|
2604
|
+
* const { contextLength, source, nativeContextLength } =
|
|
2605
|
+
* await client.models.getContextLength({ model: 'gemma4' });
|
|
2606
|
+
* // -> { contextLength: 4096, source: 'running', nativeContextLength: 131072 }
|
|
2607
|
+
* ```
|
|
2608
|
+
*
|
|
2609
|
+
* Makes two server calls (`/api/ps` + `/api/show`) unless
|
|
2610
|
+
* `skipRunningCheck` is set. A 404 from `/api/ps` (endpoint without the
|
|
2611
|
+
* listing) is treated as "not running" and never fails the lookup;
|
|
2612
|
+
* other transport errors propagate normally.
|
|
2613
|
+
*/
|
|
2614
|
+
async getContextLength(request) {
|
|
2615
|
+
let running;
|
|
2616
|
+
if (request.skipRunningCheck !== true) {
|
|
2617
|
+
try {
|
|
2618
|
+
const ps = await this.ps({ signal: request.signal, timeoutMs: request.timeoutMs });
|
|
2619
|
+
running = findRunningModelContextLength(ps.models, request.model);
|
|
2620
|
+
} catch (err) {
|
|
2621
|
+
if (!(err instanceof OllamaNotFoundError)) throw err;
|
|
2622
|
+
}
|
|
2623
|
+
}
|
|
2624
|
+
const showRes = await this.show({
|
|
2625
|
+
model: request.model,
|
|
2626
|
+
signal: request.signal,
|
|
2627
|
+
timeoutMs: request.timeoutMs
|
|
2628
|
+
});
|
|
2629
|
+
return resolveContextLength({
|
|
2630
|
+
running,
|
|
2631
|
+
parameter: extractParameterNumCtx(showRes.parameters),
|
|
2632
|
+
native: extractContextLength(showRes.model_info)
|
|
2279
2633
|
});
|
|
2280
2634
|
}
|
|
2281
2635
|
version() {
|
|
@@ -4273,6 +4627,52 @@ var ResponsesModule = class {
|
|
|
4273
4627
|
};
|
|
4274
4628
|
|
|
4275
4629
|
// src/conversation.ts
|
|
4630
|
+
function compactConversationHistory(messages, options) {
|
|
4631
|
+
const reserve = Math.max(0, options.reserveForReply ?? 0);
|
|
4632
|
+
const minTail = Math.max(0, options.minTailMessages ?? 2);
|
|
4633
|
+
const budget = Math.max(0, options.maxEstimatedTokens - reserve);
|
|
4634
|
+
let split = 0;
|
|
4635
|
+
while (split < messages.length && messages[split].role === "system") split += 1;
|
|
4636
|
+
const pinned = messages.slice(0, split);
|
|
4637
|
+
const tail = messages.slice(split);
|
|
4638
|
+
let used = 0;
|
|
4639
|
+
for (const message of pinned) used += estimateMessageTokens(message);
|
|
4640
|
+
const retained = [];
|
|
4641
|
+
for (let i = tail.length - 1; i >= 0; i -= 1) {
|
|
4642
|
+
const message = tail[i];
|
|
4643
|
+
const cost = estimateMessageTokens(message);
|
|
4644
|
+
const fits = used + cost <= budget;
|
|
4645
|
+
if (!fits && retained.length >= minTail) break;
|
|
4646
|
+
retained.unshift(message);
|
|
4647
|
+
used += cost;
|
|
4648
|
+
}
|
|
4649
|
+
return [...pinned, ...retained];
|
|
4650
|
+
}
|
|
4651
|
+
function sanitizeHistoryForNextTurn(messages, options = {}) {
|
|
4652
|
+
const keep = Math.max(0, options.keepImagesOnLastMessages ?? 1);
|
|
4653
|
+
const placeholder = options.imagePlaceholder;
|
|
4654
|
+
const cutoff = messages.length - keep;
|
|
4655
|
+
const sanitized = [];
|
|
4656
|
+
for (let index = 0; index < messages.length; index += 1) {
|
|
4657
|
+
const message = messages[index];
|
|
4658
|
+
if (index >= cutoff || !message.images || message.images.length === 0) {
|
|
4659
|
+
sanitized.push(message);
|
|
4660
|
+
continue;
|
|
4661
|
+
}
|
|
4662
|
+
const { images: _evicted, ...rest } = message;
|
|
4663
|
+
if (placeholder === void 0) {
|
|
4664
|
+
sanitized.push(rest);
|
|
4665
|
+
} else {
|
|
4666
|
+
sanitized.push({
|
|
4667
|
+
...rest,
|
|
4668
|
+
content: rest.content === "" ? placeholder : `${rest.content}
|
|
4669
|
+
|
|
4670
|
+
${placeholder}`
|
|
4671
|
+
});
|
|
4672
|
+
}
|
|
4673
|
+
}
|
|
4674
|
+
return sanitized;
|
|
4675
|
+
}
|
|
4276
4676
|
function extractTurnStats(res) {
|
|
4277
4677
|
const cachedTokens = res.prompt_eval_cached_count ?? 0;
|
|
4278
4678
|
const evaluatedTokens = res.prompt_eval_count ?? 0;
|
|
@@ -4359,6 +4759,48 @@ var ConversationSession = class {
|
|
|
4359
4759
|
getMessages() {
|
|
4360
4760
|
return this.history.map((message) => message);
|
|
4361
4761
|
}
|
|
4762
|
+
/**
|
|
4763
|
+
* Compacts the session's history in place: pinned system prompt plus the
|
|
4764
|
+
* most recent turns that fit the budget, oldest tailable turns dropped.
|
|
4765
|
+
*
|
|
4766
|
+
* This is the explicit escape hatch for sessions that outgrew their
|
|
4767
|
+
* window — `cacheStats.hitRate` collapsing toward zero while history grows
|
|
4768
|
+
* is the usual symptom, and the pre-flight warnings on `chat()` fire once
|
|
4769
|
+
* the prompt estimate crosses the threshold. After compaction the next
|
|
4770
|
+
* turn's prompt prefix differs from everything before it, so the KV cache
|
|
4771
|
+
* starts cold — visible as a one-turn `evaluatedTokens` spike in
|
|
4772
|
+
* `cacheStats`. Cumulative cache tallies are deliberately **not** reset:
|
|
4773
|
+
* they are observations, and the spike is part of the story.
|
|
4774
|
+
*
|
|
4775
|
+
* Returns the before/after estimates and the drop count. No-op (zero
|
|
4776
|
+
* drops, equal before/after estimates) when the history already fits.
|
|
4777
|
+
*/
|
|
4778
|
+
compact(options = {}) {
|
|
4779
|
+
const window = options.maxEstimatedTokens ?? this.config.options?.num_ctx ?? OLLAMA_FALLBACK_CONTEXT_LENGTH;
|
|
4780
|
+
const reserve = options.reserveForReply ?? Math.ceil(window * 0.1);
|
|
4781
|
+
const estimatedTokensBefore = this.history.reduce(
|
|
4782
|
+
(sum, message) => sum + estimateMessageTokens(message),
|
|
4783
|
+
0
|
|
4784
|
+
);
|
|
4785
|
+
const compacted = compactConversationHistory(this.history, {
|
|
4786
|
+
maxEstimatedTokens: window,
|
|
4787
|
+
reserveForReply: reserve,
|
|
4788
|
+
...options.minTailMessages !== void 0 ? { minTailMessages: options.minTailMessages } : {}
|
|
4789
|
+
});
|
|
4790
|
+
const estimatedTokensAfter = compacted.reduce(
|
|
4791
|
+
(sum, message) => sum + estimateMessageTokens(message),
|
|
4792
|
+
0
|
|
4793
|
+
);
|
|
4794
|
+
const droppedMessages = this.history.length - compacted.length;
|
|
4795
|
+
this.history.length = 0;
|
|
4796
|
+
this.history.push(...compacted);
|
|
4797
|
+
return {
|
|
4798
|
+
droppedMessages,
|
|
4799
|
+
estimatedTokensBefore,
|
|
4800
|
+
estimatedTokensAfter,
|
|
4801
|
+
effectiveBudget: window - reserve
|
|
4802
|
+
};
|
|
4803
|
+
}
|
|
4362
4804
|
/**
|
|
4363
4805
|
* Clears the conversation back to its initial state (system prompt, zero
|
|
4364
4806
|
* turns, zeroed cache statistics). The next turn starts with a cold cache
|
|
@@ -4377,103 +4819,6 @@ var ConversationSession = class {
|
|
|
4377
4819
|
}
|
|
4378
4820
|
};
|
|
4379
4821
|
|
|
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
4822
|
// src/embed-batch.ts
|
|
4478
4823
|
var DEFAULT_EMBED_BATCH_SIZE = 32;
|
|
4479
4824
|
var DEFAULT_EMBED_CONCURRENCY = 3;
|
|
@@ -5098,7 +5443,7 @@ function createLogicalRequestId() {
|
|
|
5098
5443
|
logicalRequestSequence += 1;
|
|
5099
5444
|
return `ollama-request-${logicalRequestSequence}`;
|
|
5100
5445
|
}
|
|
5101
|
-
var OllamaClient = class {
|
|
5446
|
+
var OllamaClient = class _OllamaClient {
|
|
5102
5447
|
registry;
|
|
5103
5448
|
models;
|
|
5104
5449
|
retryConfig;
|
|
@@ -5130,6 +5475,16 @@ var OllamaClient = class {
|
|
|
5130
5475
|
* (streams, via `executeWithFailover`'s `holdUntil` release path).
|
|
5131
5476
|
*/
|
|
5132
5477
|
activeRequests = /* @__PURE__ */ new Set();
|
|
5478
|
+
/**
|
|
5479
|
+
* Dynamic multi-host model-affinity routing, active only when
|
|
5480
|
+
* `config.endpointHealth.modelAffinity` is set — see
|
|
5481
|
+
* `src/providers/model-affinity-router.ts` and ADR 0028. `undefined` means
|
|
5482
|
+
* the request path skips reordering entirely (zero overhead, and no `/api/ps`
|
|
5483
|
+
* probes are ever issued).
|
|
5484
|
+
*/
|
|
5485
|
+
modelAffinity;
|
|
5486
|
+
/** Default timeout for one-off probe requests (`ping`, affinity `/api/ps` refreshes). */
|
|
5487
|
+
static PROBE_TIMEOUT_MS = 5e3;
|
|
5133
5488
|
constructor(config = {}) {
|
|
5134
5489
|
this.cloudApiKey = resolveApiKey(config.apiKey);
|
|
5135
5490
|
const defaultBaseUrl = resolveBaseUrl(config.baseUrl);
|
|
@@ -5157,6 +5512,11 @@ var OllamaClient = class {
|
|
|
5157
5512
|
this.defaultContextLength = config.defaultContextLength;
|
|
5158
5513
|
this.contextWarningThreshold = config.contextWarningThreshold;
|
|
5159
5514
|
this.onContextOverflow = config.onContextOverflow ?? "warn";
|
|
5515
|
+
const modelAffinityOptions = config.endpointHealth?.modelAffinity;
|
|
5516
|
+
this.modelAffinity = modelAffinityOptions !== void 0 ? new ModelAffinityRouter(
|
|
5517
|
+
(endpoint) => this.fetchEndpointRunningModels(endpoint),
|
|
5518
|
+
modelAffinityOptions
|
|
5519
|
+
) : void 0;
|
|
5160
5520
|
this.models = new ModelsClient((op, opts) => this.executeWithFailover(op, opts));
|
|
5161
5521
|
}
|
|
5162
5522
|
get modelsClient() {
|
|
@@ -5367,7 +5727,8 @@ var OllamaClient = class {
|
|
|
5367
5727
|
);
|
|
5368
5728
|
continue;
|
|
5369
5729
|
}
|
|
5370
|
-
|
|
5730
|
+
const ordered = this.modelAffinity !== void 0 ? this.modelAffinity.reorder(runnable, options?.model) : runnable;
|
|
5731
|
+
for (const [attemptIndex, endpoint] of ordered.entries()) {
|
|
5371
5732
|
this.logger.debug(`Executing on endpoint "${endpoint.name}" (${endpoint.baseUrl})`);
|
|
5372
5733
|
const http = new HttpClient({
|
|
5373
5734
|
baseUrl: endpoint.baseUrl,
|
|
@@ -5668,7 +6029,15 @@ var OllamaClient = class {
|
|
|
5668
6029
|
createModel = (r) => this.models.create(r);
|
|
5669
6030
|
deleteModel = (req) => this.models.delete(req);
|
|
5670
6031
|
copyModel = (req) => this.models.copy(req);
|
|
5671
|
-
ps = () => this.models.ps();
|
|
6032
|
+
ps = (request) => this.models.ps(request);
|
|
6033
|
+
/**
|
|
6034
|
+
* Discovers the model's real context window — the allocated window of the
|
|
6035
|
+
* running instance (`/api/ps`), the Modelfile `num_ctx` default, and the
|
|
6036
|
+
* native GGUF maximum (`/api/show` `model_info`), resolved by precedence.
|
|
6037
|
+
* Convenience alias for {@link ModelsClient.getContextLength} — see that
|
|
6038
|
+
* method for the full contract and the precedence order.
|
|
6039
|
+
*/
|
|
6040
|
+
getContextLength = (request) => this.models.getContextLength(request);
|
|
5672
6041
|
version = () => this.models.version();
|
|
5673
6042
|
createBlob = (digest, data) => this.models.createBlob(digest, data);
|
|
5674
6043
|
checkBlob = (digest) => this.models.checkBlob(digest);
|
|
@@ -5881,9 +6250,114 @@ var OllamaClient = class {
|
|
|
5881
6250
|
)
|
|
5882
6251
|
);
|
|
5883
6252
|
}
|
|
6253
|
+
/**
|
|
6254
|
+
* Ultra-lightweight liveness probe against the client's current best
|
|
6255
|
+
* endpoint: `HEAD /` — the root ping Ollama's documentation defines for
|
|
6256
|
+
* load-balancer health checks (HTTP 200; the daemon answers `GET /` with the
|
|
6257
|
+
* plain-text body "Ollama is running", which a HEAD request doesn't transfer).
|
|
6258
|
+
*
|
|
6259
|
+
* Deliberately a *single-endpoint, single-shot* probe — no failover to other
|
|
6260
|
+
* candidates, no retry — so the answer reflects one concrete host, not "some
|
|
6261
|
+
* endpoint eventually answered". To audit every configured endpoint, use
|
|
6262
|
+
* {@link healthCheck} (which probes all of them, and also reports the server
|
|
6263
|
+
* version at the cost of the heavier `GET /api/version` round trip).
|
|
6264
|
+
*
|
|
6265
|
+
* Resolves — never throws — with `{ healthy: false, error }` for connection
|
|
6266
|
+
* failures and timeouts; only caller-initiated cancellation (`signal`/
|
|
6267
|
+
* `destroy()`) rejects, mirroring how any other aborted request behaves.
|
|
6268
|
+
*/
|
|
6269
|
+
async ping(options) {
|
|
6270
|
+
const endpoint = this.registry.candidates()[0];
|
|
6271
|
+
if (endpoint === void 0) {
|
|
6272
|
+
return { healthy: false, latencyMs: 0, baseUrl: "", error: "No configured endpoints" };
|
|
6273
|
+
}
|
|
6274
|
+
const http = new HttpClient({
|
|
6275
|
+
baseUrl: endpoint.baseUrl,
|
|
6276
|
+
...endpoint.apiKey !== void 0 ? { apiKey: endpoint.apiKey } : {},
|
|
6277
|
+
...endpoint.headers !== void 0 ? { headers: endpoint.headers } : {},
|
|
6278
|
+
fetch: this.fetchImpl,
|
|
6279
|
+
middleware: this.middleware,
|
|
6280
|
+
onLifecycleEvent: this.onLifecycleEvent
|
|
6281
|
+
});
|
|
6282
|
+
const timeout = createTimeoutSignal(
|
|
6283
|
+
options?.timeoutMs ?? _OllamaClient.PROBE_TIMEOUT_MS,
|
|
6284
|
+
options?.signal
|
|
6285
|
+
);
|
|
6286
|
+
const startTime = Date.now();
|
|
6287
|
+
try {
|
|
6288
|
+
await http.request({ path: "/", method: "HEAD", signal: timeout.signal });
|
|
6289
|
+
return { healthy: true, latencyMs: Date.now() - startTime, baseUrl: endpoint.baseUrl };
|
|
6290
|
+
} catch (err) {
|
|
6291
|
+
if (options?.signal?.aborted) {
|
|
6292
|
+
throw err instanceof Error ? err : new Error(String(err));
|
|
6293
|
+
}
|
|
6294
|
+
return {
|
|
6295
|
+
healthy: false,
|
|
6296
|
+
latencyMs: Date.now() - startTime,
|
|
6297
|
+
baseUrl: endpoint.baseUrl,
|
|
6298
|
+
error: err instanceof Error ? err.message : String(err)
|
|
6299
|
+
};
|
|
6300
|
+
} finally {
|
|
6301
|
+
timeout.cancel();
|
|
6302
|
+
}
|
|
6303
|
+
}
|
|
6304
|
+
/**
|
|
6305
|
+
* Affinity-cache snapshots for every configured endpoint — which models each
|
|
6306
|
+
* host held in VRAM at the last successful `/api/ps` probe, when that was, and
|
|
6307
|
+
* whether it's still fresh. Empty when `endpointHealth.modelAffinity` isn't
|
|
6308
|
+
* configured (no probes are ever issued, so there is nothing to report).
|
|
6309
|
+
*/
|
|
6310
|
+
modelAffinityStatus() {
|
|
6311
|
+
return this.modelAffinity?.status(this.registry.list()) ?? [];
|
|
6312
|
+
}
|
|
6313
|
+
/**
|
|
6314
|
+
* Explicitly refreshes the model-affinity cache for every configured endpoint
|
|
6315
|
+
* (deduped with any in-flight background refresh). No-op — resolves
|
|
6316
|
+
* immediately — when `endpointHealth.modelAffinity` isn't configured. Use it
|
|
6317
|
+
* to warm the cache at startup so the very first request already routes by
|
|
6318
|
+
* residency instead of waiting for the self-warming first-round refresh.
|
|
6319
|
+
*/
|
|
6320
|
+
async warmModelAffinity() {
|
|
6321
|
+
await this.modelAffinity?.warm(this.registry.list());
|
|
6322
|
+
}
|
|
5884
6323
|
endpointStatus() {
|
|
5885
6324
|
return this.registry.status();
|
|
5886
6325
|
}
|
|
6326
|
+
/**
|
|
6327
|
+
* Fetches one endpoint's `GET /api/ps` running-model list for the affinity
|
|
6328
|
+
* router. Bypasses `executeWithFailover` deliberately: probes must not
|
|
6329
|
+
* reenter the routing machinery they inform, must not touch the registry's
|
|
6330
|
+
* circuit-breaker/failure bookkeeping (a dead host's probe failing must not
|
|
6331
|
+
* cool it down harder than its real traffic already does), and must not fail
|
|
6332
|
+
* over to a different host (the answer is per-host by definition). Failures
|
|
6333
|
+
* propagate to the router, which records a short backoff and retries later.
|
|
6334
|
+
*/
|
|
6335
|
+
async fetchEndpointRunningModels(endpoint) {
|
|
6336
|
+
var _stack = [];
|
|
6337
|
+
try {
|
|
6338
|
+
const http = new HttpClient({
|
|
6339
|
+
baseUrl: endpoint.baseUrl,
|
|
6340
|
+
...endpoint.apiKey !== void 0 ? { apiKey: endpoint.apiKey } : {},
|
|
6341
|
+
...endpoint.headers !== void 0 ? { headers: endpoint.headers } : {},
|
|
6342
|
+
fetch: this.fetchImpl,
|
|
6343
|
+
middleware: this.middleware,
|
|
6344
|
+
onLifecycleEvent: this.onLifecycleEvent
|
|
6345
|
+
});
|
|
6346
|
+
const controller = new AbortController();
|
|
6347
|
+
const timer = setTimeout(() => controller.abort(), _OllamaClient.PROBE_TIMEOUT_MS);
|
|
6348
|
+
const _timer = __using(_stack, disposableTimer(timer));
|
|
6349
|
+
const res = await http.request({
|
|
6350
|
+
path: "/api/ps",
|
|
6351
|
+
method: "GET",
|
|
6352
|
+
signal: controller.signal
|
|
6353
|
+
});
|
|
6354
|
+
return res.models ?? [];
|
|
6355
|
+
} catch (_) {
|
|
6356
|
+
var _error = _, _hasError = true;
|
|
6357
|
+
} finally {
|
|
6358
|
+
__callDispose(_stack, _error, _hasError);
|
|
6359
|
+
}
|
|
6360
|
+
}
|
|
5887
6361
|
/**
|
|
5888
6362
|
* Immediately aborts every in-flight request and active stream managed by
|
|
5889
6363
|
* this client — the clean teardown path for short-lived processes, CLI
|
|
@@ -6916,11 +7390,29 @@ ${systemPrompt}`
|
|
|
6916
7390
|
}
|
|
6917
7391
|
|
|
6918
7392
|
// src/agent/agent.ts
|
|
7393
|
+
var SIGNATURE_REPORT_LIMIT = 200;
|
|
7394
|
+
function stableStringify(value) {
|
|
7395
|
+
if (value === null || typeof value !== "object") {
|
|
7396
|
+
return JSON.stringify(value) ?? "null";
|
|
7397
|
+
}
|
|
7398
|
+
if (Array.isArray(value)) {
|
|
7399
|
+
return `[${value.map((entry) => stableStringify(entry)).join(",")}]`;
|
|
7400
|
+
}
|
|
7401
|
+
const entries = Object.entries(value).filter(([, entryValue]) => entryValue !== void 0).sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0).map(([key, entryValue]) => `${JSON.stringify(key)}:${stableStringify(entryValue)}`);
|
|
7402
|
+
return `{${entries.join(",")}}`;
|
|
7403
|
+
}
|
|
7404
|
+
function canonicalToolCallSignature(toolCall) {
|
|
7405
|
+
return `${toolCall.function.name}(${stableStringify(toolCall.function.arguments)})`;
|
|
7406
|
+
}
|
|
7407
|
+
function reportableSignature(signature) {
|
|
7408
|
+
return signature.length <= SIGNATURE_REPORT_LIMIT ? signature : `${signature.slice(0, SIGNATURE_REPORT_LIMIT)}\u2026`;
|
|
7409
|
+
}
|
|
6919
7410
|
var Agent = class {
|
|
6920
7411
|
client;
|
|
6921
7412
|
tools;
|
|
6922
7413
|
maxIterations;
|
|
6923
7414
|
maxToolCalls;
|
|
7415
|
+
maxRepeatedToolCalls;
|
|
6924
7416
|
hooks;
|
|
6925
7417
|
validateToolCapability;
|
|
6926
7418
|
toolContextSize;
|
|
@@ -6929,10 +7421,14 @@ var Agent = class {
|
|
|
6929
7421
|
this.tools = config.tools;
|
|
6930
7422
|
this.maxIterations = config.maxIterations ?? 10;
|
|
6931
7423
|
this.maxToolCalls = config.maxToolCalls;
|
|
7424
|
+
this.maxRepeatedToolCalls = config.maxRepeatedToolCalls;
|
|
6932
7425
|
this.hooks = config.hooks;
|
|
6933
7426
|
if (this.maxToolCalls !== void 0 && (!Number.isInteger(this.maxToolCalls) || this.maxToolCalls < 0)) {
|
|
6934
7427
|
throw new RangeError("Agent maxToolCalls must be a non-negative integer");
|
|
6935
7428
|
}
|
|
7429
|
+
if (this.maxRepeatedToolCalls !== void 0 && (!Number.isInteger(this.maxRepeatedToolCalls) || this.maxRepeatedToolCalls < 1)) {
|
|
7430
|
+
throw new RangeError("Agent maxRepeatedToolCalls must be a positive integer");
|
|
7431
|
+
}
|
|
6936
7432
|
this.validateToolCapability = config.validateToolCapability ?? true;
|
|
6937
7433
|
this.toolContextSize = config.toolContextSize ?? 32768;
|
|
6938
7434
|
if (!Number.isInteger(this.toolContextSize) || this.toolContextSize <= 0) {
|
|
@@ -6979,6 +7475,7 @@ var Agent = class {
|
|
|
6979
7475
|
)
|
|
6980
7476
|
} : input.options;
|
|
6981
7477
|
let toolCallsExecuted = 0;
|
|
7478
|
+
const signatureCounts = /* @__PURE__ */ new Map();
|
|
6982
7479
|
for (let iteration = 1; iteration <= this.maxIterations; iteration++) {
|
|
6983
7480
|
const outcome = await withSpan(
|
|
6984
7481
|
"ollama.agent.turn",
|
|
@@ -7010,6 +7507,24 @@ var Agent = class {
|
|
|
7010
7507
|
{ maxToolCalls: this.maxToolCalls, toolCallsExecuted }
|
|
7011
7508
|
);
|
|
7012
7509
|
}
|
|
7510
|
+
if (this.maxRepeatedToolCalls !== void 0) {
|
|
7511
|
+
for (const tc of toolCalls) {
|
|
7512
|
+
const signature = canonicalToolCallSignature(tc);
|
|
7513
|
+
const executions = (signatureCounts.get(signature) ?? 0) + 1;
|
|
7514
|
+
if (executions > this.maxRepeatedToolCalls) {
|
|
7515
|
+
throw new OllamaAgentToolLoopError(
|
|
7516
|
+
`Agent would repeat tool call "${tc.function.name}" ${executions} times (max ${this.maxRepeatedToolCalls}) without changing arguments \u2014 stuck in a tool loop`,
|
|
7517
|
+
{
|
|
7518
|
+
toolName: tc.function.name,
|
|
7519
|
+
repeatedExecutions: executions,
|
|
7520
|
+
maxRepeatedToolCalls: this.maxRepeatedToolCalls,
|
|
7521
|
+
signature: reportableSignature(signature)
|
|
7522
|
+
}
|
|
7523
|
+
);
|
|
7524
|
+
}
|
|
7525
|
+
signatureCounts.set(signature, executions);
|
|
7526
|
+
}
|
|
7527
|
+
}
|
|
7013
7528
|
for (const tc of toolCalls) {
|
|
7014
7529
|
this.hooks?.onToolCallStart?.(tc);
|
|
7015
7530
|
}
|
|
@@ -7042,6 +7557,160 @@ var Agent = class {
|
|
|
7042
7557
|
}
|
|
7043
7558
|
};
|
|
7044
7559
|
|
|
7560
|
+
// src/affinity.ts
|
|
7561
|
+
var DEFAULT_CONCURRENT_MODELS = 1;
|
|
7562
|
+
var DEFAULT_PER_MODEL_CONCURRENCY = 1;
|
|
7563
|
+
var DEFAULT_PS_CACHE_TTL_MS = 3e4;
|
|
7564
|
+
var ModelAffinityScheduler = class {
|
|
7565
|
+
constructor(client, options = {}) {
|
|
7566
|
+
this.client = client;
|
|
7567
|
+
this.concurrentModels = options.concurrentModels ?? DEFAULT_CONCURRENT_MODELS;
|
|
7568
|
+
this.perModelConcurrency = options.perModelConcurrency ?? DEFAULT_PER_MODEL_CONCURRENCY;
|
|
7569
|
+
this.psCacheTtlMs = options.psCacheTtlMs ?? DEFAULT_PS_CACHE_TTL_MS;
|
|
7570
|
+
if (!Number.isInteger(this.concurrentModels) || this.concurrentModels < 1) {
|
|
7571
|
+
throw new OllamaClientError("concurrentModels must be an integer >= 1.", {
|
|
7572
|
+
code: "invalid_request"
|
|
7573
|
+
});
|
|
7574
|
+
}
|
|
7575
|
+
if (!Number.isInteger(this.perModelConcurrency) || this.perModelConcurrency < 1) {
|
|
7576
|
+
throw new OllamaClientError("perModelConcurrency must be an integer >= 1.", {
|
|
7577
|
+
code: "invalid_request"
|
|
7578
|
+
});
|
|
7579
|
+
}
|
|
7580
|
+
if (!Number.isFinite(this.psCacheTtlMs) || this.psCacheTtlMs < 0) {
|
|
7581
|
+
throw new OllamaClientError("psCacheTtlMs must be a non-negative number.", {
|
|
7582
|
+
code: "invalid_request"
|
|
7583
|
+
});
|
|
7584
|
+
}
|
|
7585
|
+
}
|
|
7586
|
+
queues = /* @__PURE__ */ new Map();
|
|
7587
|
+
concurrentModels;
|
|
7588
|
+
perModelConcurrency;
|
|
7589
|
+
psCacheTtlMs;
|
|
7590
|
+
psCache;
|
|
7591
|
+
drainWaiters = [];
|
|
7592
|
+
/**
|
|
7593
|
+
* Runs `task` on the scheduler, serialized per model. When `model` is a
|
|
7594
|
+
* list of candidates, the already-loaded one is preferred (fresh
|
|
7595
|
+
* `/api/ps` data subject to the TTL cache); with none loaded — or when the
|
|
7596
|
+
* lookup fails — the first candidate runs. The chosen model is handed to
|
|
7597
|
+
* the task.
|
|
7598
|
+
*/
|
|
7599
|
+
async run(model, task) {
|
|
7600
|
+
const candidates = typeof model === "string" ? [model] : [...model];
|
|
7601
|
+
if (candidates.length === 0 || candidates.some((name) => typeof name !== "string" || name === "")) {
|
|
7602
|
+
throw new OllamaClientError("run() requires at least one non-empty model name.", {
|
|
7603
|
+
code: "invalid_request"
|
|
7604
|
+
});
|
|
7605
|
+
}
|
|
7606
|
+
const chosen = candidates.length === 1 ? candidates[0] : await this.chooseCandidate(candidates);
|
|
7607
|
+
return new Promise((resolve, reject) => {
|
|
7608
|
+
const queue = this.queues.get(chosen) ?? { items: [], active: 0 };
|
|
7609
|
+
this.queues.set(chosen, queue);
|
|
7610
|
+
queue.items.push({
|
|
7611
|
+
task,
|
|
7612
|
+
resolve,
|
|
7613
|
+
reject
|
|
7614
|
+
});
|
|
7615
|
+
this.pump();
|
|
7616
|
+
});
|
|
7617
|
+
}
|
|
7618
|
+
/**
|
|
7619
|
+
* Names of the models currently loaded, per `GET /api/ps`, cached for
|
|
7620
|
+
* `psCacheTtlMs`. Degrades to an empty list when the server can't answer
|
|
7621
|
+
* (never throws; candidate selection treats it as "nothing loaded").
|
|
7622
|
+
*/
|
|
7623
|
+
async loadedModels() {
|
|
7624
|
+
const names = await this.refreshPsNames();
|
|
7625
|
+
return [...names];
|
|
7626
|
+
}
|
|
7627
|
+
/** Current scheduling state — active models and queued task count. */
|
|
7628
|
+
get stats() {
|
|
7629
|
+
const activeModels = [];
|
|
7630
|
+
let queuedTasks = 0;
|
|
7631
|
+
for (const [name, queue] of this.queues) {
|
|
7632
|
+
if (queue.active > 0) activeModels.push(name);
|
|
7633
|
+
queuedTasks += queue.items.length;
|
|
7634
|
+
}
|
|
7635
|
+
return { activeModels, queuedTasks };
|
|
7636
|
+
}
|
|
7637
|
+
/**
|
|
7638
|
+
* Resolves once every queue is empty and no task is running. Does not
|
|
7639
|
+
* abort anything — tasks are caller-owned; abort them via their own
|
|
7640
|
+
* signals before disposing if needed.
|
|
7641
|
+
*/
|
|
7642
|
+
async dispose() {
|
|
7643
|
+
if (this.stats.queuedTasks === 0 && this.stats.activeModels.length === 0) return;
|
|
7644
|
+
await new Promise((resolve) => {
|
|
7645
|
+
this.drainWaiters.push(resolve);
|
|
7646
|
+
});
|
|
7647
|
+
}
|
|
7648
|
+
async chooseCandidate(candidates) {
|
|
7649
|
+
const loaded = await this.refreshPsNames();
|
|
7650
|
+
for (const candidate of candidates) {
|
|
7651
|
+
if (loaded.has(candidate)) return candidate;
|
|
7652
|
+
}
|
|
7653
|
+
return candidates[0];
|
|
7654
|
+
}
|
|
7655
|
+
async refreshPsNames() {
|
|
7656
|
+
const now = Date.now();
|
|
7657
|
+
if (this.psCache !== void 0 && now - this.psCache.at <= this.psCacheTtlMs) {
|
|
7658
|
+
return this.psCache.names;
|
|
7659
|
+
}
|
|
7660
|
+
let names;
|
|
7661
|
+
try {
|
|
7662
|
+
const ps = await this.client.models.ps();
|
|
7663
|
+
names = new Set(ps.models.map((entry) => entry.name));
|
|
7664
|
+
} catch {
|
|
7665
|
+
return /* @__PURE__ */ new Set();
|
|
7666
|
+
}
|
|
7667
|
+
this.psCache = { at: now, names };
|
|
7668
|
+
return names;
|
|
7669
|
+
}
|
|
7670
|
+
pump() {
|
|
7671
|
+
for (const [name, queue] of this.queues) {
|
|
7672
|
+
while (queue.items.length > 0 && queue.active < this.perModelConcurrency) {
|
|
7673
|
+
if (queue.active === 0 && this.activeModelCount() >= this.concurrentModels) {
|
|
7674
|
+
break;
|
|
7675
|
+
}
|
|
7676
|
+
const entry = queue.items.shift();
|
|
7677
|
+
queue.active += 1;
|
|
7678
|
+
this.execute(name, queue, entry);
|
|
7679
|
+
}
|
|
7680
|
+
}
|
|
7681
|
+
}
|
|
7682
|
+
activeModelCount() {
|
|
7683
|
+
let count = 0;
|
|
7684
|
+
for (const queue of this.queues.values()) {
|
|
7685
|
+
if (queue.active > 0) count += 1;
|
|
7686
|
+
}
|
|
7687
|
+
return count;
|
|
7688
|
+
}
|
|
7689
|
+
execute(name, queue, entry) {
|
|
7690
|
+
void (async () => {
|
|
7691
|
+
try {
|
|
7692
|
+
entry.resolve(await entry.task(name));
|
|
7693
|
+
} catch (err) {
|
|
7694
|
+
entry.reject(err);
|
|
7695
|
+
} finally {
|
|
7696
|
+
queue.active -= 1;
|
|
7697
|
+
if (queue.items.length === 0 && queue.active === 0) {
|
|
7698
|
+
this.queues.delete(name);
|
|
7699
|
+
}
|
|
7700
|
+
if (this.stats.queuedTasks === 0 && this.stats.activeModels.length === 0) {
|
|
7701
|
+
const waiters = [...this.drainWaiters];
|
|
7702
|
+
this.drainWaiters.length = 0;
|
|
7703
|
+
for (const resolve of waiters) resolve();
|
|
7704
|
+
}
|
|
7705
|
+
this.pump();
|
|
7706
|
+
}
|
|
7707
|
+
})();
|
|
7708
|
+
}
|
|
7709
|
+
};
|
|
7710
|
+
function createModelAffinityScheduler(client, options) {
|
|
7711
|
+
return new ModelAffinityScheduler(client, options);
|
|
7712
|
+
}
|
|
7713
|
+
|
|
7045
7714
|
// src/system-one.ts
|
|
7046
7715
|
var MAX_SYSTEM_ONE_IMAGES_BYTES = 32 * 1024 * 1024;
|
|
7047
7716
|
var MAX_SYSTEM_ONE_REQUEST_BYTES = 64 * 1024;
|
|
@@ -7088,7 +7757,10 @@ exports.KEEP_ALIVE_UNLOAD = KEEP_ALIVE_UNLOAD;
|
|
|
7088
7757
|
exports.KNOWN_DONE_REASONS = KNOWN_DONE_REASONS;
|
|
7089
7758
|
exports.MAX_SYSTEM_ONE_IMAGES_BYTES = MAX_SYSTEM_ONE_IMAGES_BYTES;
|
|
7090
7759
|
exports.MAX_SYSTEM_ONE_REQUEST_BYTES = MAX_SYSTEM_ONE_REQUEST_BYTES;
|
|
7760
|
+
exports.MESSAGE_OVERHEAD_TOKENS = MESSAGE_OVERHEAD_TOKENS;
|
|
7091
7761
|
exports.McpBridge = McpBridge;
|
|
7762
|
+
exports.ModelAffinityRouter = ModelAffinityRouter;
|
|
7763
|
+
exports.ModelAffinityScheduler = ModelAffinityScheduler;
|
|
7092
7764
|
exports.ModelsClient = ModelsClient;
|
|
7093
7765
|
exports.NANOS_PER_MS = NANOS_PER_MS;
|
|
7094
7766
|
exports.NANOS_PER_SECOND = NANOS_PER_SECOND;
|
|
@@ -7098,6 +7770,7 @@ exports.OLLAMA_FALLBACK_CONTEXT_LENGTH = OLLAMA_FALLBACK_CONTEXT_LENGTH;
|
|
|
7098
7770
|
exports.OllamaAbortError = OllamaAbortError;
|
|
7099
7771
|
exports.OllamaAgentMaxIterationsError = OllamaAgentMaxIterationsError;
|
|
7100
7772
|
exports.OllamaAgentMaxToolCallsError = OllamaAgentMaxToolCallsError;
|
|
7773
|
+
exports.OllamaAgentToolLoopError = OllamaAgentToolLoopError;
|
|
7101
7774
|
exports.OllamaAuthError = OllamaAuthError;
|
|
7102
7775
|
exports.OllamaBadGatewayError = OllamaBadGatewayError;
|
|
7103
7776
|
exports.OllamaClient = OllamaClient;
|
|
@@ -7138,13 +7811,16 @@ exports.addSpanEvent = addSpanEvent;
|
|
|
7138
7811
|
exports.applySkill = applySkill;
|
|
7139
7812
|
exports.batchEmbed = batchEmbed;
|
|
7140
7813
|
exports.calculateBackoff = calculateBackoff;
|
|
7814
|
+
exports.canonicalToolCallSignature = canonicalToolCallSignature;
|
|
7141
7815
|
exports.checkChatContext = checkChatContext;
|
|
7142
7816
|
exports.checkEndpointHealth = checkEndpointHealth;
|
|
7143
7817
|
exports.checkGenerateContext = checkGenerateContext;
|
|
7818
|
+
exports.compactConversationHistory = compactConversationHistory;
|
|
7144
7819
|
exports.composeMiddleware = composeMiddleware;
|
|
7145
7820
|
exports.contextWarningMessage = contextWarningMessage;
|
|
7146
7821
|
exports.createConsoleLogger = createConsoleLogger;
|
|
7147
7822
|
exports.createDecision = createDecision;
|
|
7823
|
+
exports.createModelAffinityScheduler = createModelAffinityScheduler;
|
|
7148
7824
|
exports.createOllamaCloudFreeTierQuota = createOllamaCloudFreeTierQuota;
|
|
7149
7825
|
exports.createTimeoutSignal = createTimeoutSignal;
|
|
7150
7826
|
exports.defineTool = defineTool;
|
|
@@ -7153,15 +7829,21 @@ exports.embedBatchOverflowMessage = embedBatchOverflowMessage;
|
|
|
7153
7829
|
exports.encodeImage = encodeImage;
|
|
7154
7830
|
exports.estimateChatRequestTokens = estimateChatRequestTokens;
|
|
7155
7831
|
exports.estimateGenerateRequestTokens = estimateGenerateRequestTokens;
|
|
7832
|
+
exports.estimateMessageTokens = estimateMessageTokens;
|
|
7156
7833
|
exports.estimateSystemOneRequestBytes = estimateSystemOneRequestBytes;
|
|
7157
7834
|
exports.estimateTokens = estimateTokens;
|
|
7835
|
+
exports.extractNativeContextLength = extractContextLength;
|
|
7836
|
+
exports.extractParameterNumCtx = extractParameterNumCtx;
|
|
7158
7837
|
exports.extractUsage = extractUsage;
|
|
7159
7838
|
exports.fetchWithBackoff = fetchWithBackoff;
|
|
7160
7839
|
exports.findOversizedEmbedInputs = findOversizedEmbedInputs;
|
|
7840
|
+
exports.findRunningModelContextLength = findRunningModelContextLength;
|
|
7841
|
+
exports.formatTelemetry = formatTelemetry;
|
|
7161
7842
|
exports.imageStringNeedsResolution = imageStringNeedsResolution;
|
|
7162
7843
|
exports.inferRuntimeMode = inferRuntimeMode;
|
|
7163
7844
|
exports.isKeepAliveSugar = isKeepAliveSugar;
|
|
7164
7845
|
exports.isKnownDoneReason = isKnownDoneReason;
|
|
7846
|
+
exports.isModelResident = isModelResident;
|
|
7165
7847
|
exports.isSameToolCall = isSameToolCall;
|
|
7166
7848
|
exports.listAllMcpTools = listAllMcpTools;
|
|
7167
7849
|
exports.listAvailableModels = listAvailableModels;
|
|
@@ -7179,8 +7861,10 @@ exports.parseNdjsonStream = parseNdjsonStream;
|
|
|
7179
7861
|
exports.parseSseStream = parseSseStream;
|
|
7180
7862
|
exports.parseStructuredOutput = parseStructuredOutput;
|
|
7181
7863
|
exports.registerMcpTools = registerMcpTools;
|
|
7864
|
+
exports.resolveContextLength = resolveContextLength;
|
|
7182
7865
|
exports.resolveCredentialEndpoints = resolveCredentialEndpoints;
|
|
7183
7866
|
exports.resolveImageInput = resolveImageInput;
|
|
7867
|
+
exports.sanitizeHistoryForNextTurn = sanitizeHistoryForNextTurn;
|
|
7184
7868
|
exports.setSpanError = setSpanError;
|
|
7185
7869
|
exports.toDataStream = toDataStream;
|
|
7186
7870
|
exports.toResponse = toResponse;
|