@nemesis-oss/ollama-sdk 1.10.0 → 1.11.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 +44 -0
- package/README.md +176 -1
- package/dist/{anthropic-CPaoxeLX.d.ts → anthropic-CfricAhK.d.ts} +1 -1
- package/dist/{anthropic-DuE8M6Tz.d.cts → anthropic-DinrABof.d.cts} +1 -1
- package/dist/generated-api.d.cts +4 -4
- package/dist/generated-api.d.ts +4 -4
- package/dist/generated-runtime.cjs.map +1 -1
- package/dist/generated-runtime.d.cts +1 -1
- package/dist/generated-runtime.d.ts +1 -1
- package/dist/generated-runtime.js.map +1 -1
- package/dist/index.cjs +538 -104
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +583 -94
- package/dist/index.d.ts +583 -94
- package/dist/index.js +527 -105
- package/dist/index.js.map +1 -1
- package/dist/mcp-generated.d.cts +1 -1
- package/dist/mcp-generated.d.ts +1 -1
- package/dist/{runtime-BV9SK_cf.d.ts → runtime-CCm4CtU_.d.ts} +28 -1
- package/dist/{runtime-BsdBbuOv.d.cts → runtime-DTtWCxDs.d.cts} +28 -1
- package/dist/skills.cjs.map +1 -1
- package/dist/skills.js.map +1 -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) {
|
|
@@ -1192,7 +1218,7 @@ var HttpClient = class {
|
|
|
1192
1218
|
}
|
|
1193
1219
|
}
|
|
1194
1220
|
async request(options) {
|
|
1195
|
-
const url =
|
|
1221
|
+
const url = joinUrlPath(this.baseUrl, options.path);
|
|
1196
1222
|
const method = options.method ?? (options.body !== void 0 || options.rawBody !== void 0 ? "POST" : "GET");
|
|
1197
1223
|
const headers = this.buildHeaders(options.headers);
|
|
1198
1224
|
if (options.rawBody !== void 0 && !options.headers?.["Content-Type"]) {
|
|
@@ -1252,7 +1278,7 @@ var HttpClient = class {
|
|
|
1252
1278
|
* can decode their provider-specific event payloads without duplicating transport logic.
|
|
1253
1279
|
*/
|
|
1254
1280
|
async requestSseStream(options) {
|
|
1255
|
-
const url =
|
|
1281
|
+
const url = joinUrlPath(this.baseUrl, options.path);
|
|
1256
1282
|
const method = options.method ?? "POST";
|
|
1257
1283
|
const headers = {
|
|
1258
1284
|
...this.buildHeaders(options.headers),
|
|
@@ -1319,7 +1345,7 @@ var HttpClient = class {
|
|
|
1319
1345
|
);
|
|
1320
1346
|
}
|
|
1321
1347
|
async requestStream(options) {
|
|
1322
|
-
const url =
|
|
1348
|
+
const url = joinUrlPath(this.baseUrl, options.path);
|
|
1323
1349
|
const method = options.method ?? "POST";
|
|
1324
1350
|
const headers = this.buildHeaders(options.headers);
|
|
1325
1351
|
return withSpan(
|
|
@@ -2160,6 +2186,151 @@ function createTimeoutSignal(timeoutMs, userSignal) {
|
|
|
2160
2186
|
return { signal: controller.signal, cancel };
|
|
2161
2187
|
}
|
|
2162
2188
|
|
|
2189
|
+
// src/context-safety.ts
|
|
2190
|
+
var DEFAULT_CONTEXT_WARNING_THRESHOLD = 0.9;
|
|
2191
|
+
var IMAGE_TOKEN_ESTIMATE = 300;
|
|
2192
|
+
var CHARS_PER_TOKEN = 4;
|
|
2193
|
+
function isCjkCodePoint(code) {
|
|
2194
|
+
return code >= 19968 && code <= 40959 || // CJK Unified Ideographs
|
|
2195
|
+
code >= 13312 && code <= 19903 || // CJK Extension A
|
|
2196
|
+
code >= 12352 && code <= 12543 || // Hiragana + Katakana
|
|
2197
|
+
code >= 44032 && code <= 55215 || // Hangul syllables
|
|
2198
|
+
code >= 63744 && code <= 64255 || // CJK Compatibility Ideographs
|
|
2199
|
+
code >= 65280 && code <= 65519;
|
|
2200
|
+
}
|
|
2201
|
+
function estimateTokens(text) {
|
|
2202
|
+
if (text.length === 0) return 0;
|
|
2203
|
+
let cjk = 0;
|
|
2204
|
+
let other = 0;
|
|
2205
|
+
for (const char of text) {
|
|
2206
|
+
if (isCjkCodePoint(char.codePointAt(0) ?? 0)) cjk += 1;
|
|
2207
|
+
else other += 1;
|
|
2208
|
+
}
|
|
2209
|
+
return Math.ceil(cjk + other / CHARS_PER_TOKEN);
|
|
2210
|
+
}
|
|
2211
|
+
var MESSAGE_OVERHEAD_TOKENS = 4;
|
|
2212
|
+
function estimateMessageTokens(message) {
|
|
2213
|
+
let total = MESSAGE_OVERHEAD_TOKENS + estimateTokens(message.content ?? "");
|
|
2214
|
+
if (message.thinking !== void 0) total += estimateTokens(message.thinking);
|
|
2215
|
+
if (message.tool_calls !== void 0) {
|
|
2216
|
+
for (const call of message.tool_calls) {
|
|
2217
|
+
total += estimateTokens(call.function.name);
|
|
2218
|
+
total += estimateTokens(JSON.stringify(call.function.arguments ?? {}));
|
|
2219
|
+
}
|
|
2220
|
+
}
|
|
2221
|
+
if (message.images !== void 0) total += message.images.length * IMAGE_TOKEN_ESTIMATE;
|
|
2222
|
+
return total;
|
|
2223
|
+
}
|
|
2224
|
+
function estimateChatRequestTokens(req) {
|
|
2225
|
+
let total = 0;
|
|
2226
|
+
for (const message of req.messages) total += estimateMessageTokens(message);
|
|
2227
|
+
if (req.tools !== void 0) {
|
|
2228
|
+
for (const tool of req.tools) {
|
|
2229
|
+
total += estimateTokens(tool.function.description ?? "");
|
|
2230
|
+
total += estimateTokens(JSON.stringify(tool.function.parameters ?? {}));
|
|
2231
|
+
}
|
|
2232
|
+
}
|
|
2233
|
+
return total;
|
|
2234
|
+
}
|
|
2235
|
+
function estimateGenerateRequestTokens(req) {
|
|
2236
|
+
let total = estimateTokens(req.prompt ?? "");
|
|
2237
|
+
if (req.system !== void 0) total += estimateTokens(req.system);
|
|
2238
|
+
if (req.template !== void 0) total += estimateTokens(req.template);
|
|
2239
|
+
if (req.images !== void 0) total += req.images.length * IMAGE_TOKEN_ESTIMATE;
|
|
2240
|
+
return total;
|
|
2241
|
+
}
|
|
2242
|
+
function checkChatContext(req, options = {}) {
|
|
2243
|
+
return check(estimateChatRequestTokens(req), req.options?.num_ctx, options);
|
|
2244
|
+
}
|
|
2245
|
+
function checkGenerateContext(req, options = {}) {
|
|
2246
|
+
return check(estimateGenerateRequestTokens(req), req.options?.num_ctx, options);
|
|
2247
|
+
}
|
|
2248
|
+
function check(estimatedPromptTokens, requestNumCtx, options) {
|
|
2249
|
+
const threshold = options.threshold ?? DEFAULT_CONTEXT_WARNING_THRESHOLD;
|
|
2250
|
+
if (requestNumCtx !== void 0) {
|
|
2251
|
+
return {
|
|
2252
|
+
estimatedPromptTokens,
|
|
2253
|
+
effectiveContextLength: requestNumCtx,
|
|
2254
|
+
windowSource: "request",
|
|
2255
|
+
exceedsThreshold: estimatedPromptTokens > requestNumCtx * threshold
|
|
2256
|
+
};
|
|
2257
|
+
}
|
|
2258
|
+
if (options.defaultContextLength !== void 0) {
|
|
2259
|
+
return {
|
|
2260
|
+
estimatedPromptTokens,
|
|
2261
|
+
effectiveContextLength: options.defaultContextLength,
|
|
2262
|
+
windowSource: "client-default",
|
|
2263
|
+
exceedsThreshold: estimatedPromptTokens > options.defaultContextLength * threshold
|
|
2264
|
+
};
|
|
2265
|
+
}
|
|
2266
|
+
return {
|
|
2267
|
+
estimatedPromptTokens,
|
|
2268
|
+
effectiveContextLength: OLLAMA_FALLBACK_CONTEXT_LENGTH,
|
|
2269
|
+
windowSource: "server-fallback",
|
|
2270
|
+
exceedsThreshold: estimatedPromptTokens > OLLAMA_FALLBACK_CONTEXT_LENGTH
|
|
2271
|
+
};
|
|
2272
|
+
}
|
|
2273
|
+
var OLLAMA_FALLBACK_CONTEXT_LENGTH = 2048;
|
|
2274
|
+
function contextWarningMessage(check2, kind) {
|
|
2275
|
+
const where = `OllamaClient.${kind}()`;
|
|
2276
|
+
const estimate = `~${check2.estimatedPromptTokens} tokens`;
|
|
2277
|
+
if (check2.windowSource === "request") {
|
|
2278
|
+
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\`.`;
|
|
2279
|
+
}
|
|
2280
|
+
if (check2.windowSource === "client-default") {
|
|
2281
|
+
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\`.`;
|
|
2282
|
+
}
|
|
2283
|
+
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\`.`;
|
|
2284
|
+
}
|
|
2285
|
+
|
|
2286
|
+
// src/context-discovery.ts
|
|
2287
|
+
function extractParameterNumCtx(parameters) {
|
|
2288
|
+
if (!parameters) return void 0;
|
|
2289
|
+
const match = /^[ \t]*num_ctx[ \t]+(\d+)\s*$/m.exec(parameters);
|
|
2290
|
+
if (match === null) return void 0;
|
|
2291
|
+
const value = Number.parseInt(match[1], 10);
|
|
2292
|
+
return Number.isFinite(value) && value > 0 ? value : void 0;
|
|
2293
|
+
}
|
|
2294
|
+
function findRunningModelContextLength(psModels, model) {
|
|
2295
|
+
for (const entry of psModels) {
|
|
2296
|
+
const exact = entry.name === model || entry.model === model;
|
|
2297
|
+
const taglessPrefix = !model.includes(":") && (entry.name.startsWith(`${model}:`) || entry.model.startsWith(`${model}:`));
|
|
2298
|
+
if (exact || taglessPrefix) {
|
|
2299
|
+
return typeof entry.context_length === "number" && Number.isFinite(entry.context_length) && entry.context_length > 0 ? entry.context_length : void 0;
|
|
2300
|
+
}
|
|
2301
|
+
}
|
|
2302
|
+
return void 0;
|
|
2303
|
+
}
|
|
2304
|
+
function positiveInt(value) {
|
|
2305
|
+
return typeof value === "number" && Number.isFinite(value) && value > 0 ? value : void 0;
|
|
2306
|
+
}
|
|
2307
|
+
function resolveContextLength(signals) {
|
|
2308
|
+
const running = positiveInt(signals.running);
|
|
2309
|
+
const parameter = positiveInt(signals.parameter);
|
|
2310
|
+
const native = positiveInt(signals.native);
|
|
2311
|
+
if (running !== void 0) {
|
|
2312
|
+
return {
|
|
2313
|
+
contextLength: running,
|
|
2314
|
+
source: "running",
|
|
2315
|
+
...parameter !== void 0 ? { parameterContextLength: parameter } : {},
|
|
2316
|
+
...native !== void 0 ? { nativeContextLength: native } : {},
|
|
2317
|
+
runningContextLength: running
|
|
2318
|
+
};
|
|
2319
|
+
}
|
|
2320
|
+
if (parameter !== void 0) {
|
|
2321
|
+
return {
|
|
2322
|
+
contextLength: parameter,
|
|
2323
|
+
source: "parameters",
|
|
2324
|
+
parameterContextLength: parameter,
|
|
2325
|
+
...native !== void 0 ? { nativeContextLength: native } : {}
|
|
2326
|
+
};
|
|
2327
|
+
}
|
|
2328
|
+
if (native !== void 0) {
|
|
2329
|
+
return { contextLength: native, source: "model-info", nativeContextLength: native };
|
|
2330
|
+
}
|
|
2331
|
+
return { contextLength: OLLAMA_FALLBACK_CONTEXT_LENGTH, source: "fallback" };
|
|
2332
|
+
}
|
|
2333
|
+
|
|
2163
2334
|
// src/keep-alive.ts
|
|
2164
2335
|
var KEEP_ALIVE_UNLOAD = 0;
|
|
2165
2336
|
var KEEP_ALIVE_INDEFINITE = -1;
|
|
@@ -2273,9 +2444,66 @@ var ModelsClient = class {
|
|
|
2273
2444
|
{ ...request, singleEndpoint: true }
|
|
2274
2445
|
);
|
|
2275
2446
|
}
|
|
2276
|
-
ps() {
|
|
2277
|
-
return this.runner(
|
|
2278
|
-
|
|
2447
|
+
ps(request) {
|
|
2448
|
+
return this.runner(
|
|
2449
|
+
(http, signal) => http.request({ path: "/api/ps", method: "GET", signal }),
|
|
2450
|
+
{
|
|
2451
|
+
singleEndpoint: true,
|
|
2452
|
+
...request?.signal !== void 0 ? { signal: request.signal } : {},
|
|
2453
|
+
...request?.timeoutMs !== void 0 ? { timeoutMs: request.timeoutMs } : {}
|
|
2454
|
+
}
|
|
2455
|
+
);
|
|
2456
|
+
}
|
|
2457
|
+
/**
|
|
2458
|
+
* Discovers the model's real context window from the server — no more
|
|
2459
|
+
* 2048/4096 guesswork. Consults, in precedence order:
|
|
2460
|
+
*
|
|
2461
|
+
* 1. `GET /api/ps` — the window the **running instance actually
|
|
2462
|
+
* allocated** (exact, reflects the Modelfile `num_ctx` default and
|
|
2463
|
+
* what fit in memory); skipped when the model isn't loaded, when
|
|
2464
|
+
* `skipRunningCheck` is set, or harmlessly on servers without the
|
|
2465
|
+
* field.
|
|
2466
|
+
* 2. `POST /api/show` `parameters` — a Modelfile-authored
|
|
2467
|
+
* `num_ctx <n>` default, i.e. the window a cold load will allocate.
|
|
2468
|
+
* 3. `POST /api/show` `model_info["<arch>.context_length"]` — the
|
|
2469
|
+
* model's **native GGUF maximum** (a ceiling to raise `num_ctx`
|
|
2470
|
+
* toward, not the allocated window).
|
|
2471
|
+
* 4. Fallback — Ollama's conservative unset-`num_ctx` default (2048).
|
|
2472
|
+
*
|
|
2473
|
+
* The result carries every signal it found, so `nativeContextLength:
|
|
2474
|
+
* 131072` vs `contextLength: 4096` (a 32× gap seen in the official API
|
|
2475
|
+
* examples) is visible rather than guessed. Typical wiring:
|
|
2476
|
+
*
|
|
2477
|
+
* ```ts
|
|
2478
|
+
* const { contextLength, source, nativeContextLength } =
|
|
2479
|
+
* await client.models.getContextLength({ model: 'gemma4' });
|
|
2480
|
+
* // -> { contextLength: 4096, source: 'running', nativeContextLength: 131072 }
|
|
2481
|
+
* ```
|
|
2482
|
+
*
|
|
2483
|
+
* Makes two server calls (`/api/ps` + `/api/show`) unless
|
|
2484
|
+
* `skipRunningCheck` is set. A 404 from `/api/ps` (endpoint without the
|
|
2485
|
+
* listing) is treated as "not running" and never fails the lookup;
|
|
2486
|
+
* other transport errors propagate normally.
|
|
2487
|
+
*/
|
|
2488
|
+
async getContextLength(request) {
|
|
2489
|
+
let running;
|
|
2490
|
+
if (request.skipRunningCheck !== true) {
|
|
2491
|
+
try {
|
|
2492
|
+
const ps = await this.ps({ signal: request.signal, timeoutMs: request.timeoutMs });
|
|
2493
|
+
running = findRunningModelContextLength(ps.models, request.model);
|
|
2494
|
+
} catch (err) {
|
|
2495
|
+
if (!(err instanceof OllamaNotFoundError)) throw err;
|
|
2496
|
+
}
|
|
2497
|
+
}
|
|
2498
|
+
const showRes = await this.show({
|
|
2499
|
+
model: request.model,
|
|
2500
|
+
signal: request.signal,
|
|
2501
|
+
timeoutMs: request.timeoutMs
|
|
2502
|
+
});
|
|
2503
|
+
return resolveContextLength({
|
|
2504
|
+
running,
|
|
2505
|
+
parameter: extractParameterNumCtx(showRes.parameters),
|
|
2506
|
+
native: extractContextLength(showRes.model_info)
|
|
2279
2507
|
});
|
|
2280
2508
|
}
|
|
2281
2509
|
version() {
|
|
@@ -4273,6 +4501,52 @@ var ResponsesModule = class {
|
|
|
4273
4501
|
};
|
|
4274
4502
|
|
|
4275
4503
|
// src/conversation.ts
|
|
4504
|
+
function compactConversationHistory(messages, options) {
|
|
4505
|
+
const reserve = Math.max(0, options.reserveForReply ?? 0);
|
|
4506
|
+
const minTail = Math.max(0, options.minTailMessages ?? 2);
|
|
4507
|
+
const budget = Math.max(0, options.maxEstimatedTokens - reserve);
|
|
4508
|
+
let split = 0;
|
|
4509
|
+
while (split < messages.length && messages[split].role === "system") split += 1;
|
|
4510
|
+
const pinned = messages.slice(0, split);
|
|
4511
|
+
const tail = messages.slice(split);
|
|
4512
|
+
let used = 0;
|
|
4513
|
+
for (const message of pinned) used += estimateMessageTokens(message);
|
|
4514
|
+
const retained = [];
|
|
4515
|
+
for (let i = tail.length - 1; i >= 0; i -= 1) {
|
|
4516
|
+
const message = tail[i];
|
|
4517
|
+
const cost = estimateMessageTokens(message);
|
|
4518
|
+
const fits = used + cost <= budget;
|
|
4519
|
+
if (!fits && retained.length >= minTail) break;
|
|
4520
|
+
retained.unshift(message);
|
|
4521
|
+
used += cost;
|
|
4522
|
+
}
|
|
4523
|
+
return [...pinned, ...retained];
|
|
4524
|
+
}
|
|
4525
|
+
function sanitizeHistoryForNextTurn(messages, options = {}) {
|
|
4526
|
+
const keep = Math.max(0, options.keepImagesOnLastMessages ?? 1);
|
|
4527
|
+
const placeholder = options.imagePlaceholder;
|
|
4528
|
+
const cutoff = messages.length - keep;
|
|
4529
|
+
const sanitized = [];
|
|
4530
|
+
for (let index = 0; index < messages.length; index += 1) {
|
|
4531
|
+
const message = messages[index];
|
|
4532
|
+
if (index >= cutoff || !message.images || message.images.length === 0) {
|
|
4533
|
+
sanitized.push(message);
|
|
4534
|
+
continue;
|
|
4535
|
+
}
|
|
4536
|
+
const { images: _evicted, ...rest } = message;
|
|
4537
|
+
if (placeholder === void 0) {
|
|
4538
|
+
sanitized.push(rest);
|
|
4539
|
+
} else {
|
|
4540
|
+
sanitized.push({
|
|
4541
|
+
...rest,
|
|
4542
|
+
content: rest.content === "" ? placeholder : `${rest.content}
|
|
4543
|
+
|
|
4544
|
+
${placeholder}`
|
|
4545
|
+
});
|
|
4546
|
+
}
|
|
4547
|
+
}
|
|
4548
|
+
return sanitized;
|
|
4549
|
+
}
|
|
4276
4550
|
function extractTurnStats(res) {
|
|
4277
4551
|
const cachedTokens = res.prompt_eval_cached_count ?? 0;
|
|
4278
4552
|
const evaluatedTokens = res.prompt_eval_count ?? 0;
|
|
@@ -4359,6 +4633,48 @@ var ConversationSession = class {
|
|
|
4359
4633
|
getMessages() {
|
|
4360
4634
|
return this.history.map((message) => message);
|
|
4361
4635
|
}
|
|
4636
|
+
/**
|
|
4637
|
+
* Compacts the session's history in place: pinned system prompt plus the
|
|
4638
|
+
* most recent turns that fit the budget, oldest tailable turns dropped.
|
|
4639
|
+
*
|
|
4640
|
+
* This is the explicit escape hatch for sessions that outgrew their
|
|
4641
|
+
* window — `cacheStats.hitRate` collapsing toward zero while history grows
|
|
4642
|
+
* is the usual symptom, and the pre-flight warnings on `chat()` fire once
|
|
4643
|
+
* the prompt estimate crosses the threshold. After compaction the next
|
|
4644
|
+
* turn's prompt prefix differs from everything before it, so the KV cache
|
|
4645
|
+
* starts cold — visible as a one-turn `evaluatedTokens` spike in
|
|
4646
|
+
* `cacheStats`. Cumulative cache tallies are deliberately **not** reset:
|
|
4647
|
+
* they are observations, and the spike is part of the story.
|
|
4648
|
+
*
|
|
4649
|
+
* Returns the before/after estimates and the drop count. No-op (zero
|
|
4650
|
+
* drops, equal before/after estimates) when the history already fits.
|
|
4651
|
+
*/
|
|
4652
|
+
compact(options = {}) {
|
|
4653
|
+
const window = options.maxEstimatedTokens ?? this.config.options?.num_ctx ?? OLLAMA_FALLBACK_CONTEXT_LENGTH;
|
|
4654
|
+
const reserve = options.reserveForReply ?? Math.ceil(window * 0.1);
|
|
4655
|
+
const estimatedTokensBefore = this.history.reduce(
|
|
4656
|
+
(sum, message) => sum + estimateMessageTokens(message),
|
|
4657
|
+
0
|
|
4658
|
+
);
|
|
4659
|
+
const compacted = compactConversationHistory(this.history, {
|
|
4660
|
+
maxEstimatedTokens: window,
|
|
4661
|
+
reserveForReply: reserve,
|
|
4662
|
+
...options.minTailMessages !== void 0 ? { minTailMessages: options.minTailMessages } : {}
|
|
4663
|
+
});
|
|
4664
|
+
const estimatedTokensAfter = compacted.reduce(
|
|
4665
|
+
(sum, message) => sum + estimateMessageTokens(message),
|
|
4666
|
+
0
|
|
4667
|
+
);
|
|
4668
|
+
const droppedMessages = this.history.length - compacted.length;
|
|
4669
|
+
this.history.length = 0;
|
|
4670
|
+
this.history.push(...compacted);
|
|
4671
|
+
return {
|
|
4672
|
+
droppedMessages,
|
|
4673
|
+
estimatedTokensBefore,
|
|
4674
|
+
estimatedTokensAfter,
|
|
4675
|
+
effectiveBudget: window - reserve
|
|
4676
|
+
};
|
|
4677
|
+
}
|
|
4362
4678
|
/**
|
|
4363
4679
|
* Clears the conversation back to its initial state (system prompt, zero
|
|
4364
4680
|
* turns, zeroed cache statistics). The next turn starts with a cold cache
|
|
@@ -4377,103 +4693,6 @@ var ConversationSession = class {
|
|
|
4377
4693
|
}
|
|
4378
4694
|
};
|
|
4379
4695
|
|
|
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
4696
|
// src/embed-batch.ts
|
|
4478
4697
|
var DEFAULT_EMBED_BATCH_SIZE = 32;
|
|
4479
4698
|
var DEFAULT_EMBED_CONCURRENCY = 3;
|
|
@@ -5668,7 +5887,15 @@ var OllamaClient = class {
|
|
|
5668
5887
|
createModel = (r) => this.models.create(r);
|
|
5669
5888
|
deleteModel = (req) => this.models.delete(req);
|
|
5670
5889
|
copyModel = (req) => this.models.copy(req);
|
|
5671
|
-
ps = () => this.models.ps();
|
|
5890
|
+
ps = (request) => this.models.ps(request);
|
|
5891
|
+
/**
|
|
5892
|
+
* Discovers the model's real context window — the allocated window of the
|
|
5893
|
+
* running instance (`/api/ps`), the Modelfile `num_ctx` default, and the
|
|
5894
|
+
* native GGUF maximum (`/api/show` `model_info`), resolved by precedence.
|
|
5895
|
+
* Convenience alias for {@link ModelsClient.getContextLength} — see that
|
|
5896
|
+
* method for the full contract and the precedence order.
|
|
5897
|
+
*/
|
|
5898
|
+
getContextLength = (request) => this.models.getContextLength(request);
|
|
5672
5899
|
version = () => this.models.version();
|
|
5673
5900
|
createBlob = (digest, data) => this.models.createBlob(digest, data);
|
|
5674
5901
|
checkBlob = (digest) => this.models.checkBlob(digest);
|
|
@@ -6916,11 +7143,29 @@ ${systemPrompt}`
|
|
|
6916
7143
|
}
|
|
6917
7144
|
|
|
6918
7145
|
// src/agent/agent.ts
|
|
7146
|
+
var SIGNATURE_REPORT_LIMIT = 200;
|
|
7147
|
+
function stableStringify(value) {
|
|
7148
|
+
if (value === null || typeof value !== "object") {
|
|
7149
|
+
return JSON.stringify(value) ?? "null";
|
|
7150
|
+
}
|
|
7151
|
+
if (Array.isArray(value)) {
|
|
7152
|
+
return `[${value.map((entry) => stableStringify(entry)).join(",")}]`;
|
|
7153
|
+
}
|
|
7154
|
+
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)}`);
|
|
7155
|
+
return `{${entries.join(",")}}`;
|
|
7156
|
+
}
|
|
7157
|
+
function canonicalToolCallSignature(toolCall) {
|
|
7158
|
+
return `${toolCall.function.name}(${stableStringify(toolCall.function.arguments)})`;
|
|
7159
|
+
}
|
|
7160
|
+
function reportableSignature(signature) {
|
|
7161
|
+
return signature.length <= SIGNATURE_REPORT_LIMIT ? signature : `${signature.slice(0, SIGNATURE_REPORT_LIMIT)}\u2026`;
|
|
7162
|
+
}
|
|
6919
7163
|
var Agent = class {
|
|
6920
7164
|
client;
|
|
6921
7165
|
tools;
|
|
6922
7166
|
maxIterations;
|
|
6923
7167
|
maxToolCalls;
|
|
7168
|
+
maxRepeatedToolCalls;
|
|
6924
7169
|
hooks;
|
|
6925
7170
|
validateToolCapability;
|
|
6926
7171
|
toolContextSize;
|
|
@@ -6929,10 +7174,14 @@ var Agent = class {
|
|
|
6929
7174
|
this.tools = config.tools;
|
|
6930
7175
|
this.maxIterations = config.maxIterations ?? 10;
|
|
6931
7176
|
this.maxToolCalls = config.maxToolCalls;
|
|
7177
|
+
this.maxRepeatedToolCalls = config.maxRepeatedToolCalls;
|
|
6932
7178
|
this.hooks = config.hooks;
|
|
6933
7179
|
if (this.maxToolCalls !== void 0 && (!Number.isInteger(this.maxToolCalls) || this.maxToolCalls < 0)) {
|
|
6934
7180
|
throw new RangeError("Agent maxToolCalls must be a non-negative integer");
|
|
6935
7181
|
}
|
|
7182
|
+
if (this.maxRepeatedToolCalls !== void 0 && (!Number.isInteger(this.maxRepeatedToolCalls) || this.maxRepeatedToolCalls < 1)) {
|
|
7183
|
+
throw new RangeError("Agent maxRepeatedToolCalls must be a positive integer");
|
|
7184
|
+
}
|
|
6936
7185
|
this.validateToolCapability = config.validateToolCapability ?? true;
|
|
6937
7186
|
this.toolContextSize = config.toolContextSize ?? 32768;
|
|
6938
7187
|
if (!Number.isInteger(this.toolContextSize) || this.toolContextSize <= 0) {
|
|
@@ -6979,6 +7228,7 @@ var Agent = class {
|
|
|
6979
7228
|
)
|
|
6980
7229
|
} : input.options;
|
|
6981
7230
|
let toolCallsExecuted = 0;
|
|
7231
|
+
const signatureCounts = /* @__PURE__ */ new Map();
|
|
6982
7232
|
for (let iteration = 1; iteration <= this.maxIterations; iteration++) {
|
|
6983
7233
|
const outcome = await withSpan(
|
|
6984
7234
|
"ollama.agent.turn",
|
|
@@ -7010,6 +7260,24 @@ var Agent = class {
|
|
|
7010
7260
|
{ maxToolCalls: this.maxToolCalls, toolCallsExecuted }
|
|
7011
7261
|
);
|
|
7012
7262
|
}
|
|
7263
|
+
if (this.maxRepeatedToolCalls !== void 0) {
|
|
7264
|
+
for (const tc of toolCalls) {
|
|
7265
|
+
const signature = canonicalToolCallSignature(tc);
|
|
7266
|
+
const executions = (signatureCounts.get(signature) ?? 0) + 1;
|
|
7267
|
+
if (executions > this.maxRepeatedToolCalls) {
|
|
7268
|
+
throw new OllamaAgentToolLoopError(
|
|
7269
|
+
`Agent would repeat tool call "${tc.function.name}" ${executions} times (max ${this.maxRepeatedToolCalls}) without changing arguments \u2014 stuck in a tool loop`,
|
|
7270
|
+
{
|
|
7271
|
+
toolName: tc.function.name,
|
|
7272
|
+
repeatedExecutions: executions,
|
|
7273
|
+
maxRepeatedToolCalls: this.maxRepeatedToolCalls,
|
|
7274
|
+
signature: reportableSignature(signature)
|
|
7275
|
+
}
|
|
7276
|
+
);
|
|
7277
|
+
}
|
|
7278
|
+
signatureCounts.set(signature, executions);
|
|
7279
|
+
}
|
|
7280
|
+
}
|
|
7013
7281
|
for (const tc of toolCalls) {
|
|
7014
7282
|
this.hooks?.onToolCallStart?.(tc);
|
|
7015
7283
|
}
|
|
@@ -7042,6 +7310,160 @@ var Agent = class {
|
|
|
7042
7310
|
}
|
|
7043
7311
|
};
|
|
7044
7312
|
|
|
7313
|
+
// src/affinity.ts
|
|
7314
|
+
var DEFAULT_CONCURRENT_MODELS = 1;
|
|
7315
|
+
var DEFAULT_PER_MODEL_CONCURRENCY = 1;
|
|
7316
|
+
var DEFAULT_PS_CACHE_TTL_MS = 3e4;
|
|
7317
|
+
var ModelAffinityScheduler = class {
|
|
7318
|
+
constructor(client, options = {}) {
|
|
7319
|
+
this.client = client;
|
|
7320
|
+
this.concurrentModels = options.concurrentModels ?? DEFAULT_CONCURRENT_MODELS;
|
|
7321
|
+
this.perModelConcurrency = options.perModelConcurrency ?? DEFAULT_PER_MODEL_CONCURRENCY;
|
|
7322
|
+
this.psCacheTtlMs = options.psCacheTtlMs ?? DEFAULT_PS_CACHE_TTL_MS;
|
|
7323
|
+
if (!Number.isInteger(this.concurrentModels) || this.concurrentModels < 1) {
|
|
7324
|
+
throw new OllamaClientError("concurrentModels must be an integer >= 1.", {
|
|
7325
|
+
code: "invalid_request"
|
|
7326
|
+
});
|
|
7327
|
+
}
|
|
7328
|
+
if (!Number.isInteger(this.perModelConcurrency) || this.perModelConcurrency < 1) {
|
|
7329
|
+
throw new OllamaClientError("perModelConcurrency must be an integer >= 1.", {
|
|
7330
|
+
code: "invalid_request"
|
|
7331
|
+
});
|
|
7332
|
+
}
|
|
7333
|
+
if (!Number.isFinite(this.psCacheTtlMs) || this.psCacheTtlMs < 0) {
|
|
7334
|
+
throw new OllamaClientError("psCacheTtlMs must be a non-negative number.", {
|
|
7335
|
+
code: "invalid_request"
|
|
7336
|
+
});
|
|
7337
|
+
}
|
|
7338
|
+
}
|
|
7339
|
+
queues = /* @__PURE__ */ new Map();
|
|
7340
|
+
concurrentModels;
|
|
7341
|
+
perModelConcurrency;
|
|
7342
|
+
psCacheTtlMs;
|
|
7343
|
+
psCache;
|
|
7344
|
+
drainWaiters = [];
|
|
7345
|
+
/**
|
|
7346
|
+
* Runs `task` on the scheduler, serialized per model. When `model` is a
|
|
7347
|
+
* list of candidates, the already-loaded one is preferred (fresh
|
|
7348
|
+
* `/api/ps` data subject to the TTL cache); with none loaded — or when the
|
|
7349
|
+
* lookup fails — the first candidate runs. The chosen model is handed to
|
|
7350
|
+
* the task.
|
|
7351
|
+
*/
|
|
7352
|
+
async run(model, task) {
|
|
7353
|
+
const candidates = typeof model === "string" ? [model] : [...model];
|
|
7354
|
+
if (candidates.length === 0 || candidates.some((name) => typeof name !== "string" || name === "")) {
|
|
7355
|
+
throw new OllamaClientError("run() requires at least one non-empty model name.", {
|
|
7356
|
+
code: "invalid_request"
|
|
7357
|
+
});
|
|
7358
|
+
}
|
|
7359
|
+
const chosen = candidates.length === 1 ? candidates[0] : await this.chooseCandidate(candidates);
|
|
7360
|
+
return new Promise((resolve, reject) => {
|
|
7361
|
+
const queue = this.queues.get(chosen) ?? { items: [], active: 0 };
|
|
7362
|
+
this.queues.set(chosen, queue);
|
|
7363
|
+
queue.items.push({
|
|
7364
|
+
task,
|
|
7365
|
+
resolve,
|
|
7366
|
+
reject
|
|
7367
|
+
});
|
|
7368
|
+
this.pump();
|
|
7369
|
+
});
|
|
7370
|
+
}
|
|
7371
|
+
/**
|
|
7372
|
+
* Names of the models currently loaded, per `GET /api/ps`, cached for
|
|
7373
|
+
* `psCacheTtlMs`. Degrades to an empty list when the server can't answer
|
|
7374
|
+
* (never throws; candidate selection treats it as "nothing loaded").
|
|
7375
|
+
*/
|
|
7376
|
+
async loadedModels() {
|
|
7377
|
+
const names = await this.refreshPsNames();
|
|
7378
|
+
return [...names];
|
|
7379
|
+
}
|
|
7380
|
+
/** Current scheduling state — active models and queued task count. */
|
|
7381
|
+
get stats() {
|
|
7382
|
+
const activeModels = [];
|
|
7383
|
+
let queuedTasks = 0;
|
|
7384
|
+
for (const [name, queue] of this.queues) {
|
|
7385
|
+
if (queue.active > 0) activeModels.push(name);
|
|
7386
|
+
queuedTasks += queue.items.length;
|
|
7387
|
+
}
|
|
7388
|
+
return { activeModels, queuedTasks };
|
|
7389
|
+
}
|
|
7390
|
+
/**
|
|
7391
|
+
* Resolves once every queue is empty and no task is running. Does not
|
|
7392
|
+
* abort anything — tasks are caller-owned; abort them via their own
|
|
7393
|
+
* signals before disposing if needed.
|
|
7394
|
+
*/
|
|
7395
|
+
async dispose() {
|
|
7396
|
+
if (this.stats.queuedTasks === 0 && this.stats.activeModels.length === 0) return;
|
|
7397
|
+
await new Promise((resolve) => {
|
|
7398
|
+
this.drainWaiters.push(resolve);
|
|
7399
|
+
});
|
|
7400
|
+
}
|
|
7401
|
+
async chooseCandidate(candidates) {
|
|
7402
|
+
const loaded = await this.refreshPsNames();
|
|
7403
|
+
for (const candidate of candidates) {
|
|
7404
|
+
if (loaded.has(candidate)) return candidate;
|
|
7405
|
+
}
|
|
7406
|
+
return candidates[0];
|
|
7407
|
+
}
|
|
7408
|
+
async refreshPsNames() {
|
|
7409
|
+
const now = Date.now();
|
|
7410
|
+
if (this.psCache !== void 0 && now - this.psCache.at <= this.psCacheTtlMs) {
|
|
7411
|
+
return this.psCache.names;
|
|
7412
|
+
}
|
|
7413
|
+
let names;
|
|
7414
|
+
try {
|
|
7415
|
+
const ps = await this.client.models.ps();
|
|
7416
|
+
names = new Set(ps.models.map((entry) => entry.name));
|
|
7417
|
+
} catch {
|
|
7418
|
+
return /* @__PURE__ */ new Set();
|
|
7419
|
+
}
|
|
7420
|
+
this.psCache = { at: now, names };
|
|
7421
|
+
return names;
|
|
7422
|
+
}
|
|
7423
|
+
pump() {
|
|
7424
|
+
for (const [name, queue] of this.queues) {
|
|
7425
|
+
while (queue.items.length > 0 && queue.active < this.perModelConcurrency) {
|
|
7426
|
+
if (queue.active === 0 && this.activeModelCount() >= this.concurrentModels) {
|
|
7427
|
+
break;
|
|
7428
|
+
}
|
|
7429
|
+
const entry = queue.items.shift();
|
|
7430
|
+
queue.active += 1;
|
|
7431
|
+
this.execute(name, queue, entry);
|
|
7432
|
+
}
|
|
7433
|
+
}
|
|
7434
|
+
}
|
|
7435
|
+
activeModelCount() {
|
|
7436
|
+
let count = 0;
|
|
7437
|
+
for (const queue of this.queues.values()) {
|
|
7438
|
+
if (queue.active > 0) count += 1;
|
|
7439
|
+
}
|
|
7440
|
+
return count;
|
|
7441
|
+
}
|
|
7442
|
+
execute(name, queue, entry) {
|
|
7443
|
+
void (async () => {
|
|
7444
|
+
try {
|
|
7445
|
+
entry.resolve(await entry.task(name));
|
|
7446
|
+
} catch (err) {
|
|
7447
|
+
entry.reject(err);
|
|
7448
|
+
} finally {
|
|
7449
|
+
queue.active -= 1;
|
|
7450
|
+
if (queue.items.length === 0 && queue.active === 0) {
|
|
7451
|
+
this.queues.delete(name);
|
|
7452
|
+
}
|
|
7453
|
+
if (this.stats.queuedTasks === 0 && this.stats.activeModels.length === 0) {
|
|
7454
|
+
const waiters = [...this.drainWaiters];
|
|
7455
|
+
this.drainWaiters.length = 0;
|
|
7456
|
+
for (const resolve of waiters) resolve();
|
|
7457
|
+
}
|
|
7458
|
+
this.pump();
|
|
7459
|
+
}
|
|
7460
|
+
})();
|
|
7461
|
+
}
|
|
7462
|
+
};
|
|
7463
|
+
function createModelAffinityScheduler(client, options) {
|
|
7464
|
+
return new ModelAffinityScheduler(client, options);
|
|
7465
|
+
}
|
|
7466
|
+
|
|
7045
7467
|
// src/system-one.ts
|
|
7046
7468
|
var MAX_SYSTEM_ONE_IMAGES_BYTES = 32 * 1024 * 1024;
|
|
7047
7469
|
var MAX_SYSTEM_ONE_REQUEST_BYTES = 64 * 1024;
|
|
@@ -7088,7 +7510,9 @@ exports.KEEP_ALIVE_UNLOAD = KEEP_ALIVE_UNLOAD;
|
|
|
7088
7510
|
exports.KNOWN_DONE_REASONS = KNOWN_DONE_REASONS;
|
|
7089
7511
|
exports.MAX_SYSTEM_ONE_IMAGES_BYTES = MAX_SYSTEM_ONE_IMAGES_BYTES;
|
|
7090
7512
|
exports.MAX_SYSTEM_ONE_REQUEST_BYTES = MAX_SYSTEM_ONE_REQUEST_BYTES;
|
|
7513
|
+
exports.MESSAGE_OVERHEAD_TOKENS = MESSAGE_OVERHEAD_TOKENS;
|
|
7091
7514
|
exports.McpBridge = McpBridge;
|
|
7515
|
+
exports.ModelAffinityScheduler = ModelAffinityScheduler;
|
|
7092
7516
|
exports.ModelsClient = ModelsClient;
|
|
7093
7517
|
exports.NANOS_PER_MS = NANOS_PER_MS;
|
|
7094
7518
|
exports.NANOS_PER_SECOND = NANOS_PER_SECOND;
|
|
@@ -7098,6 +7522,7 @@ exports.OLLAMA_FALLBACK_CONTEXT_LENGTH = OLLAMA_FALLBACK_CONTEXT_LENGTH;
|
|
|
7098
7522
|
exports.OllamaAbortError = OllamaAbortError;
|
|
7099
7523
|
exports.OllamaAgentMaxIterationsError = OllamaAgentMaxIterationsError;
|
|
7100
7524
|
exports.OllamaAgentMaxToolCallsError = OllamaAgentMaxToolCallsError;
|
|
7525
|
+
exports.OllamaAgentToolLoopError = OllamaAgentToolLoopError;
|
|
7101
7526
|
exports.OllamaAuthError = OllamaAuthError;
|
|
7102
7527
|
exports.OllamaBadGatewayError = OllamaBadGatewayError;
|
|
7103
7528
|
exports.OllamaClient = OllamaClient;
|
|
@@ -7138,13 +7563,16 @@ exports.addSpanEvent = addSpanEvent;
|
|
|
7138
7563
|
exports.applySkill = applySkill;
|
|
7139
7564
|
exports.batchEmbed = batchEmbed;
|
|
7140
7565
|
exports.calculateBackoff = calculateBackoff;
|
|
7566
|
+
exports.canonicalToolCallSignature = canonicalToolCallSignature;
|
|
7141
7567
|
exports.checkChatContext = checkChatContext;
|
|
7142
7568
|
exports.checkEndpointHealth = checkEndpointHealth;
|
|
7143
7569
|
exports.checkGenerateContext = checkGenerateContext;
|
|
7570
|
+
exports.compactConversationHistory = compactConversationHistory;
|
|
7144
7571
|
exports.composeMiddleware = composeMiddleware;
|
|
7145
7572
|
exports.contextWarningMessage = contextWarningMessage;
|
|
7146
7573
|
exports.createConsoleLogger = createConsoleLogger;
|
|
7147
7574
|
exports.createDecision = createDecision;
|
|
7575
|
+
exports.createModelAffinityScheduler = createModelAffinityScheduler;
|
|
7148
7576
|
exports.createOllamaCloudFreeTierQuota = createOllamaCloudFreeTierQuota;
|
|
7149
7577
|
exports.createTimeoutSignal = createTimeoutSignal;
|
|
7150
7578
|
exports.defineTool = defineTool;
|
|
@@ -7153,11 +7581,15 @@ exports.embedBatchOverflowMessage = embedBatchOverflowMessage;
|
|
|
7153
7581
|
exports.encodeImage = encodeImage;
|
|
7154
7582
|
exports.estimateChatRequestTokens = estimateChatRequestTokens;
|
|
7155
7583
|
exports.estimateGenerateRequestTokens = estimateGenerateRequestTokens;
|
|
7584
|
+
exports.estimateMessageTokens = estimateMessageTokens;
|
|
7156
7585
|
exports.estimateSystemOneRequestBytes = estimateSystemOneRequestBytes;
|
|
7157
7586
|
exports.estimateTokens = estimateTokens;
|
|
7587
|
+
exports.extractNativeContextLength = extractContextLength;
|
|
7588
|
+
exports.extractParameterNumCtx = extractParameterNumCtx;
|
|
7158
7589
|
exports.extractUsage = extractUsage;
|
|
7159
7590
|
exports.fetchWithBackoff = fetchWithBackoff;
|
|
7160
7591
|
exports.findOversizedEmbedInputs = findOversizedEmbedInputs;
|
|
7592
|
+
exports.findRunningModelContextLength = findRunningModelContextLength;
|
|
7161
7593
|
exports.imageStringNeedsResolution = imageStringNeedsResolution;
|
|
7162
7594
|
exports.inferRuntimeMode = inferRuntimeMode;
|
|
7163
7595
|
exports.isKeepAliveSugar = isKeepAliveSugar;
|
|
@@ -7179,8 +7611,10 @@ exports.parseNdjsonStream = parseNdjsonStream;
|
|
|
7179
7611
|
exports.parseSseStream = parseSseStream;
|
|
7180
7612
|
exports.parseStructuredOutput = parseStructuredOutput;
|
|
7181
7613
|
exports.registerMcpTools = registerMcpTools;
|
|
7614
|
+
exports.resolveContextLength = resolveContextLength;
|
|
7182
7615
|
exports.resolveCredentialEndpoints = resolveCredentialEndpoints;
|
|
7183
7616
|
exports.resolveImageInput = resolveImageInput;
|
|
7617
|
+
exports.sanitizeHistoryForNextTurn = sanitizeHistoryForNextTurn;
|
|
7184
7618
|
exports.setSpanError = setSpanError;
|
|
7185
7619
|
exports.toDataStream = toDataStream;
|
|
7186
7620
|
exports.toResponse = toResponse;
|