@nemesis-oss/ollama-sdk 1.0.0 → 1.3.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 +154 -0
- package/README.md +402 -11
- package/dist/{compose-DC11aDJn.d.cts → compose-CDWq8oxJ.d.cts} +80 -4
- package/dist/{compose-DC11aDJn.d.ts → compose-CDWq8oxJ.d.ts} +80 -4
- package/dist/index.cjs +504 -70
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +448 -13
- package/dist/index.d.ts +448 -13
- package/dist/index.js +498 -71
- package/dist/index.js.map +1 -1
- package/dist/skills.cjs.map +1 -1
- package/dist/skills.d.cts +2 -2
- package/dist/skills.d.ts +2 -2
- package/dist/skills.js.map +1 -1
- package/package.json +5 -4
package/dist/index.cjs
CHANGED
|
@@ -5,6 +5,7 @@ var zod = require('zod');
|
|
|
5
5
|
// src/config.ts
|
|
6
6
|
var DEFAULT_BASE_URL = "http://localhost:11434";
|
|
7
7
|
var DEFAULT_TIMEOUT_MS = 3e4;
|
|
8
|
+
var OLLAMA_CLOUD_BASE_URL = "https://ollama.com";
|
|
8
9
|
function readEnv(name) {
|
|
9
10
|
return typeof process !== "undefined" && process.env ? process.env[name] : void 0;
|
|
10
11
|
}
|
|
@@ -14,6 +15,34 @@ function resolveBaseUrl(configBaseUrl) {
|
|
|
14
15
|
function resolveApiKey(configApiKey) {
|
|
15
16
|
return configApiKey ?? readEnv("OLLAMA_API_KEY");
|
|
16
17
|
}
|
|
18
|
+
function resolveCredentialEndpoints(config) {
|
|
19
|
+
if (config.credentials === void 0) return [];
|
|
20
|
+
const modelsByCredential = /* @__PURE__ */ new Map();
|
|
21
|
+
for (const [model, bound] of Object.entries(config.modelBindings ?? {})) {
|
|
22
|
+
for (const id of typeof bound === "string" ? [bound] : bound) {
|
|
23
|
+
if (!(id in config.credentials)) {
|
|
24
|
+
throw new Error(
|
|
25
|
+
`modelBindings["${model}"] references unknown credential "${id}" \u2014 it isn't in \`credentials\`. Known credentials: ${Object.keys(config.credentials).join(", ") || "(none)"}.`
|
|
26
|
+
);
|
|
27
|
+
}
|
|
28
|
+
const list = modelsByCredential.get(id) ?? [];
|
|
29
|
+
list.push(model);
|
|
30
|
+
modelsByCredential.set(id, list);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
return Object.entries(config.credentials).map(([id, credential]) => {
|
|
34
|
+
const isDefault = id === config.defaultCredential;
|
|
35
|
+
const boundModels = isDefault ? void 0 : modelsByCredential.get(id);
|
|
36
|
+
return {
|
|
37
|
+
name: `credential:${id}`,
|
|
38
|
+
baseUrl: resolveBaseUrl(credential.baseUrl ?? config.baseUrl),
|
|
39
|
+
apiKey: credential.apiKey,
|
|
40
|
+
...credential.headers !== void 0 ? { headers: credential.headers } : {},
|
|
41
|
+
...boundModels !== void 0 ? { models: boundModels } : {},
|
|
42
|
+
priority: isDefault ? -1 : 0
|
|
43
|
+
};
|
|
44
|
+
});
|
|
45
|
+
}
|
|
17
46
|
var DEFAULT_FAILOVER_CODES = [
|
|
18
47
|
"network_error",
|
|
19
48
|
"timeout",
|
|
@@ -72,6 +101,15 @@ var OllamaRateLimitError = class extends OllamaClientError {
|
|
|
72
101
|
this.retryAfterMs = options?.retryAfterMs;
|
|
73
102
|
}
|
|
74
103
|
};
|
|
104
|
+
var OllamaQuotaExceededError = class extends OllamaClientError {
|
|
105
|
+
windowId;
|
|
106
|
+
resetAt;
|
|
107
|
+
constructor(message, options) {
|
|
108
|
+
super(message, { ...options, code: "quota_exceeded", retryable: false });
|
|
109
|
+
this.windowId = options.windowId;
|
|
110
|
+
this.resetAt = options.resetAt;
|
|
111
|
+
}
|
|
112
|
+
};
|
|
75
113
|
var OllamaServerError = class extends OllamaClientError {
|
|
76
114
|
constructor(message, options) {
|
|
77
115
|
super(message, { ...options, code: "server_error", retryable: true });
|
|
@@ -107,6 +145,15 @@ var OllamaUnsupportedCapabilityError = class extends OllamaClientError {
|
|
|
107
145
|
this.capability = options.capability;
|
|
108
146
|
}
|
|
109
147
|
};
|
|
148
|
+
var OllamaModelRoutingError = class extends OllamaClientError {
|
|
149
|
+
model;
|
|
150
|
+
availableModels;
|
|
151
|
+
constructor(message, options) {
|
|
152
|
+
super(message, { ...options, code: "model_routing_error", retryable: false });
|
|
153
|
+
this.model = options.model;
|
|
154
|
+
this.availableModels = options.availableModels;
|
|
155
|
+
}
|
|
156
|
+
};
|
|
110
157
|
var OllamaAgentMaxIterationsError = class extends OllamaClientError {
|
|
111
158
|
maxIterations;
|
|
112
159
|
constructor(message, options) {
|
|
@@ -218,17 +265,143 @@ var EndpointRegistry = class {
|
|
|
218
265
|
failureThreshold;
|
|
219
266
|
cooldownMs;
|
|
220
267
|
now;
|
|
268
|
+
strategy;
|
|
269
|
+
roundRobinCounter = 0;
|
|
270
|
+
activeCounts = /* @__PURE__ */ new Map();
|
|
271
|
+
maxConcurrentPerEndpoint;
|
|
272
|
+
waiters = [];
|
|
221
273
|
constructor(endpoints, options = {}) {
|
|
222
274
|
this.endpoints = endpoints.length > 0 ? endpoints : [{ name: "default", baseUrl: "http://localhost:11434" }];
|
|
223
275
|
this.failureThreshold = options.failureThreshold ?? 3;
|
|
224
276
|
this.cooldownMs = options.cooldownMs ?? 3e4;
|
|
225
277
|
this.now = options.now ?? Date.now;
|
|
278
|
+
this.strategy = options.strategy ?? "priority";
|
|
279
|
+
this.maxConcurrentPerEndpoint = options.maxConcurrentPerEndpoint;
|
|
280
|
+
}
|
|
281
|
+
/**
|
|
282
|
+
* Marks one more request as in-flight against `name`. Callers (`OllamaClient`) must
|
|
283
|
+
* pair every `acquire` with a `release` once that attempt finishes, success or not —
|
|
284
|
+
* see `OllamaClient.executeWithFailover`. Only affects candidate ordering when
|
|
285
|
+
* `strategy` is `'least-connections'`; harmless (just bookkeeping) otherwise, and
|
|
286
|
+
* always reflected in `status()`'s `activeRequests`.
|
|
287
|
+
*/
|
|
288
|
+
acquire(name) {
|
|
289
|
+
this.activeCounts.set(name, (this.activeCounts.get(name) ?? 0) + 1);
|
|
290
|
+
}
|
|
291
|
+
/** Releases one in-flight request marked via `acquire`, waking one waiter for `name` if any. */
|
|
292
|
+
release(name) {
|
|
293
|
+
const next = (this.activeCounts.get(name) ?? 0) - 1;
|
|
294
|
+
if (next <= 0) {
|
|
295
|
+
this.activeCounts.delete(name);
|
|
296
|
+
} else {
|
|
297
|
+
this.activeCounts.set(name, next);
|
|
298
|
+
}
|
|
299
|
+
const idx = this.waiters.findIndex((w) => w.names.includes(name));
|
|
300
|
+
if (idx !== -1) {
|
|
301
|
+
const [waiter] = this.waiters.splice(idx, 1);
|
|
302
|
+
waiter?.cleanup();
|
|
303
|
+
waiter?.resolve();
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
/**
|
|
307
|
+
* `candidates` filtered down to those currently under `maxConcurrentPerEndpoint` (all
|
|
308
|
+
* of them, unchanged, if that option is unset).
|
|
309
|
+
*/
|
|
310
|
+
filterWithCapacity(candidates) {
|
|
311
|
+
const cap = this.maxConcurrentPerEndpoint;
|
|
312
|
+
if (cap === void 0) return [...candidates];
|
|
313
|
+
return candidates.filter((ep) => (this.activeCounts.get(ep.name) ?? 0) < cap);
|
|
314
|
+
}
|
|
315
|
+
/**
|
|
316
|
+
* Resolves once any endpoint named in `names` has spare capacity under
|
|
317
|
+
* `maxConcurrentPerEndpoint` (a no-op resolve if that option is unset, or if one
|
|
318
|
+
* already does). Otherwise queues (FIFO per name) until a matching `release()` frees a
|
|
319
|
+
* slot, or rejects if `signal` aborts first — mirroring whatever aborted it
|
|
320
|
+
* (`OllamaTimeoutError`/the caller's own abort reason) so callers see the same error
|
|
321
|
+
* shape as any other in-flight request that times out or is cancelled. FIFO order is
|
|
322
|
+
* best-effort (a newly-arriving request can still win a race for a slot that just
|
|
323
|
+
* freed) — the capacity cap itself, not fairness, is the guarantee this exists for.
|
|
324
|
+
*/
|
|
325
|
+
waitForCapacity(names, signal) {
|
|
326
|
+
const cap = this.maxConcurrentPerEndpoint;
|
|
327
|
+
if (cap === void 0) return Promise.resolve();
|
|
328
|
+
if (names.some((n) => (this.activeCounts.get(n) ?? 0) < cap)) return Promise.resolve();
|
|
329
|
+
return new Promise((resolve, reject) => {
|
|
330
|
+
const waiter = { names, resolve, cleanup: () => void 0 };
|
|
331
|
+
const onAbort = () => {
|
|
332
|
+
const idx = this.waiters.indexOf(waiter);
|
|
333
|
+
if (idx !== -1) this.waiters.splice(idx, 1);
|
|
334
|
+
reject(
|
|
335
|
+
signal?.reason instanceof Error ? signal.reason : new OllamaAbortError("Aborted while waiting for endpoint capacity")
|
|
336
|
+
);
|
|
337
|
+
};
|
|
338
|
+
if (signal !== void 0) {
|
|
339
|
+
if (signal.aborted) {
|
|
340
|
+
onAbort();
|
|
341
|
+
return;
|
|
342
|
+
}
|
|
343
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
344
|
+
waiter.cleanup = () => signal.removeEventListener("abort", onAbort);
|
|
345
|
+
}
|
|
346
|
+
this.waiters.push(waiter);
|
|
347
|
+
});
|
|
348
|
+
}
|
|
349
|
+
/**
|
|
350
|
+
* Rotates each same-`priority` group in `sorted` (already sorted highest-priority-first)
|
|
351
|
+
* by one position further per call, wrapping around within the group. Priority tiers
|
|
352
|
+
* themselves are never reordered — only candidates that were already tied.
|
|
353
|
+
*/
|
|
354
|
+
applyRoundRobin(sorted) {
|
|
355
|
+
const offset = this.roundRobinCounter++;
|
|
356
|
+
const result = [];
|
|
357
|
+
let i = 0;
|
|
358
|
+
while (i < sorted.length) {
|
|
359
|
+
const priority = sorted[i]?.priority ?? 0;
|
|
360
|
+
let j = i + 1;
|
|
361
|
+
while (j < sorted.length && (sorted[j]?.priority ?? 0) === priority) j++;
|
|
362
|
+
const tier = sorted.slice(i, j);
|
|
363
|
+
const rotation = offset % tier.length;
|
|
364
|
+
result.push(...tier.slice(rotation), ...tier.slice(0, rotation));
|
|
365
|
+
i = j;
|
|
366
|
+
}
|
|
367
|
+
return result;
|
|
368
|
+
}
|
|
369
|
+
/**
|
|
370
|
+
* Reorders each same-`priority` group in `sorted` by ascending in-flight request count
|
|
371
|
+
* (from `acquire`/`release`); a stable sort, so candidates tied on active count keep
|
|
372
|
+
* their relative (priority-then-registration) order. Priority tiers themselves are
|
|
373
|
+
* never reordered.
|
|
374
|
+
*/
|
|
375
|
+
applyLeastConnections(sorted) {
|
|
376
|
+
const result = [];
|
|
377
|
+
let i = 0;
|
|
378
|
+
while (i < sorted.length) {
|
|
379
|
+
const priority = sorted[i]?.priority ?? 0;
|
|
380
|
+
let j = i + 1;
|
|
381
|
+
while (j < sorted.length && (sorted[j]?.priority ?? 0) === priority) j++;
|
|
382
|
+
const tier = [...sorted.slice(i, j)].sort(
|
|
383
|
+
(a, b) => (this.activeCounts.get(a.name) ?? 0) - (this.activeCounts.get(b.name) ?? 0)
|
|
384
|
+
);
|
|
385
|
+
result.push(...tier);
|
|
386
|
+
i = j;
|
|
387
|
+
}
|
|
388
|
+
return result;
|
|
226
389
|
}
|
|
227
|
-
|
|
390
|
+
/**
|
|
391
|
+
* Endpoints eligible for `model`, sorted healthy-first-by-priority (falling back to
|
|
392
|
+
* soonest-to-recover if every eligible endpoint is cooling down); same-priority
|
|
393
|
+
* candidates are further reordered per `options.strategy` (see
|
|
394
|
+
* `EndpointRegistryOptions.strategy`). When `model` is given, endpoints whose `models`
|
|
395
|
+
* allow-list doesn't include it are excluded entirely — see `OllamaEndpoint.models`.
|
|
396
|
+
* Omit `model` to consider every configured endpoint, regardless of any `models`
|
|
397
|
+
* restriction (used for non-model-scoped operations).
|
|
398
|
+
*/
|
|
399
|
+
candidates(model) {
|
|
228
400
|
const currentTime = this.now();
|
|
401
|
+
const scoped = model === void 0 ? this.endpoints : this.endpoints.filter((ep) => ep.models === void 0 || ep.models.includes(model));
|
|
229
402
|
const healthy = [];
|
|
230
403
|
const coolingDown = [];
|
|
231
|
-
for (const ep of
|
|
404
|
+
for (const ep of scoped) {
|
|
232
405
|
const failures = this.failureCounts.get(ep.name) ?? 0;
|
|
233
406
|
const lastFail = this.lastFailures.get(ep.name) ?? 0;
|
|
234
407
|
const isCooling = failures >= this.failureThreshold && currentTime - lastFail < this.cooldownMs;
|
|
@@ -239,7 +412,10 @@ var EndpointRegistry = class {
|
|
|
239
412
|
}
|
|
240
413
|
}
|
|
241
414
|
if (healthy.length > 0) {
|
|
242
|
-
|
|
415
|
+
const sorted = healthy.sort((a, b) => (b.priority ?? 0) - (a.priority ?? 0));
|
|
416
|
+
if (this.strategy === "round-robin") return this.applyRoundRobin(sorted);
|
|
417
|
+
if (this.strategy === "least-connections") return this.applyLeastConnections(sorted);
|
|
418
|
+
return sorted;
|
|
243
419
|
}
|
|
244
420
|
return coolingDown.sort((a, b) => a.recoveredAt - b.recoveredAt).map((c) => c.endpoint);
|
|
245
421
|
}
|
|
@@ -255,6 +431,10 @@ var EndpointRegistry = class {
|
|
|
255
431
|
list() {
|
|
256
432
|
return this.endpoints;
|
|
257
433
|
}
|
|
434
|
+
/** True if any configured endpoint restricts itself to a `models` allow-list. */
|
|
435
|
+
hasModelScopedEndpoints() {
|
|
436
|
+
return this.endpoints.some((ep) => ep.models !== void 0);
|
|
437
|
+
}
|
|
258
438
|
status() {
|
|
259
439
|
const currentTime = this.now();
|
|
260
440
|
return this.endpoints.map((ep) => {
|
|
@@ -265,7 +445,8 @@ var EndpointRegistry = class {
|
|
|
265
445
|
endpoint: ep,
|
|
266
446
|
failureCount: failures,
|
|
267
447
|
...lastFail !== void 0 ? { lastFailureTimestamp: lastFail } : {},
|
|
268
|
-
isCoolingDown
|
|
448
|
+
isCoolingDown,
|
|
449
|
+
activeRequests: this.activeCounts.get(ep.name) ?? 0
|
|
269
450
|
};
|
|
270
451
|
});
|
|
271
452
|
}
|
|
@@ -822,6 +1003,7 @@ function aggregateChat(accumulated, chunk) {
|
|
|
822
1003
|
},
|
|
823
1004
|
model: chunk.model,
|
|
824
1005
|
done: chunk.done,
|
|
1006
|
+
doneReason: chunk.done ? chunk.done_reason : accumulated.doneReason,
|
|
825
1007
|
totalDurationMs: chunk.done && chunk.total_duration !== void 0 ? chunk.total_duration / NANOS_PER_MS : accumulated.totalDurationMs,
|
|
826
1008
|
usage: chunk.done ? extractUsage(chunk) : accumulated.usage,
|
|
827
1009
|
raw: chunk
|
|
@@ -871,6 +1053,7 @@ function aggregateGenerate(accumulated, chunk) {
|
|
|
871
1053
|
response: accumulated.response + (chunk.response ?? ""),
|
|
872
1054
|
model: chunk.model,
|
|
873
1055
|
done: chunk.done,
|
|
1056
|
+
doneReason: chunk.done ? chunk.done_reason : accumulated.doneReason,
|
|
874
1057
|
totalDurationMs: chunk.done && chunk.total_duration !== void 0 ? chunk.total_duration / NANOS_PER_MS : accumulated.totalDurationMs,
|
|
875
1058
|
usage: chunk.done ? extractUsage(chunk) : accumulated.usage,
|
|
876
1059
|
raw: chunk
|
|
@@ -1022,7 +1205,7 @@ var ModelsClient = class {
|
|
|
1022
1205
|
});
|
|
1023
1206
|
return normalizeProgressStream(stream);
|
|
1024
1207
|
},
|
|
1025
|
-
{ ...request, singleEndpoint: true }
|
|
1208
|
+
{ ...request, singleEndpoint: true, holdUntil: (stream) => stream.finalResult }
|
|
1026
1209
|
);
|
|
1027
1210
|
}
|
|
1028
1211
|
return this.runner(
|
|
@@ -1045,7 +1228,7 @@ var ModelsClient = class {
|
|
|
1045
1228
|
});
|
|
1046
1229
|
return normalizeProgressStream(stream);
|
|
1047
1230
|
},
|
|
1048
|
-
{ ...request, singleEndpoint: true }
|
|
1231
|
+
{ ...request, singleEndpoint: true, holdUntil: (stream) => stream.finalResult }
|
|
1049
1232
|
);
|
|
1050
1233
|
}
|
|
1051
1234
|
return this.runner(
|
|
@@ -1068,7 +1251,7 @@ var ModelsClient = class {
|
|
|
1068
1251
|
});
|
|
1069
1252
|
return normalizeProgressStream(stream);
|
|
1070
1253
|
},
|
|
1071
|
-
{ ...request, singleEndpoint: true }
|
|
1254
|
+
{ ...request, singleEndpoint: true, holdUntil: (stream) => stream.finalResult }
|
|
1072
1255
|
);
|
|
1073
1256
|
}
|
|
1074
1257
|
return this.runner(
|
|
@@ -1159,6 +1342,16 @@ var OpenAICompatClient = class {
|
|
|
1159
1342
|
signal
|
|
1160
1343
|
});
|
|
1161
1344
|
}
|
|
1345
|
+
async createResponses(request, signal) {
|
|
1346
|
+
return this.http.request({
|
|
1347
|
+
path: "/v1/responses",
|
|
1348
|
+
body: request,
|
|
1349
|
+
signal
|
|
1350
|
+
});
|
|
1351
|
+
}
|
|
1352
|
+
async responses(request, signal) {
|
|
1353
|
+
return this.createResponses(request, signal);
|
|
1354
|
+
}
|
|
1162
1355
|
};
|
|
1163
1356
|
|
|
1164
1357
|
// src/integrations/anthropic.ts
|
|
@@ -1179,6 +1372,32 @@ var AnthropicCompatClient = class {
|
|
|
1179
1372
|
}
|
|
1180
1373
|
};
|
|
1181
1374
|
|
|
1375
|
+
// src/utils.ts
|
|
1376
|
+
async function encodeImage(image) {
|
|
1377
|
+
if (typeof image === "string") return image;
|
|
1378
|
+
const bufferCtor = globalThis.Buffer;
|
|
1379
|
+
if (bufferCtor !== void 0) return bufferCtor.from(image).toString("base64");
|
|
1380
|
+
const CHUNK_SIZE = 32768;
|
|
1381
|
+
let binary = "";
|
|
1382
|
+
for (let i = 0; i < image.length; i += CHUNK_SIZE) {
|
|
1383
|
+
binary += String.fromCharCode(...image.subarray(i, i + CHUNK_SIZE));
|
|
1384
|
+
}
|
|
1385
|
+
return btoa(binary);
|
|
1386
|
+
}
|
|
1387
|
+
async function encodeImages(images) {
|
|
1388
|
+
if (images === void 0) return void 0;
|
|
1389
|
+
return Promise.all(images.map((image) => encodeImage(image)));
|
|
1390
|
+
}
|
|
1391
|
+
async function withEncodedImages(req) {
|
|
1392
|
+
if (!req.images?.some((image) => image instanceof Uint8Array)) return req;
|
|
1393
|
+
return { ...req, images: await encodeImages(req.images) };
|
|
1394
|
+
}
|
|
1395
|
+
async function withEncodedMessageImages(messages) {
|
|
1396
|
+
if (!messages.some((message) => message.images?.some((image) => image instanceof Uint8Array)))
|
|
1397
|
+
return messages;
|
|
1398
|
+
return Promise.all(messages.map((message) => withEncodedImages(message)));
|
|
1399
|
+
}
|
|
1400
|
+
|
|
1182
1401
|
// src/client.ts
|
|
1183
1402
|
var OllamaClient = class {
|
|
1184
1403
|
registry;
|
|
@@ -1188,17 +1407,27 @@ var OllamaClient = class {
|
|
|
1188
1407
|
failoverCodes;
|
|
1189
1408
|
fetchImpl;
|
|
1190
1409
|
logger;
|
|
1410
|
+
/**
|
|
1411
|
+
* API key for Ollama's hosted web tools (`webSearch`/`webFetch`), which always target
|
|
1412
|
+
* `OLLAMA_CLOUD_BASE_URL` regardless of `config.endpoints` — an Ollama account API key
|
|
1413
|
+
* is inherently a single global credential, not tied to any one inference endpoint.
|
|
1414
|
+
* Resolved the same way as a single-endpoint `apiKey` (`config.apiKey` falling back to
|
|
1415
|
+
* `OLLAMA_API_KEY`), independent of whether `config.endpoints` was used instead.
|
|
1416
|
+
*/
|
|
1417
|
+
cloudApiKey;
|
|
1191
1418
|
constructor(config = {}) {
|
|
1192
1419
|
const resolvedApiKey = resolveApiKey(config.apiKey);
|
|
1193
|
-
|
|
1420
|
+
this.cloudApiKey = resolvedApiKey;
|
|
1421
|
+
const credentialEndpoints = resolveCredentialEndpoints(config);
|
|
1422
|
+
const endpoints = config.endpoints ?? (credentialEndpoints.length > 0 ? [] : [
|
|
1194
1423
|
{
|
|
1195
1424
|
name: "default",
|
|
1196
1425
|
baseUrl: resolveBaseUrl(config.baseUrl),
|
|
1197
1426
|
...resolvedApiKey !== void 0 ? { apiKey: resolvedApiKey } : {},
|
|
1198
1427
|
...config.headers !== void 0 ? { headers: config.headers } : {}
|
|
1199
1428
|
}
|
|
1200
|
-
];
|
|
1201
|
-
this.registry = new EndpointRegistry(endpoints, config.endpointHealth);
|
|
1429
|
+
]);
|
|
1430
|
+
this.registry = new EndpointRegistry([...endpoints, ...credentialEndpoints], config.endpointHealth);
|
|
1202
1431
|
this.timeoutMs = config.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
1203
1432
|
this.failoverCodes = new Set(config.failoverOn ?? DEFAULT_FAILOVER_CODES);
|
|
1204
1433
|
this.fetchImpl = config.fetch ?? globalThis.fetch;
|
|
@@ -1227,53 +1456,92 @@ var OllamaClient = class {
|
|
|
1227
1456
|
async executeWithFailover(operation, options) {
|
|
1228
1457
|
const timeout = createTimeoutSignal(options?.timeoutMs ?? this.timeoutMs, options?.signal);
|
|
1229
1458
|
try {
|
|
1230
|
-
const allCandidates = this.registry.candidates();
|
|
1231
|
-
const candidates = options?.singleEndpoint ? allCandidates.slice(0, 1) : allCandidates;
|
|
1232
1459
|
let lastError;
|
|
1233
|
-
for (
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
});
|
|
1241
|
-
try {
|
|
1242
|
-
const result = await withSpan(
|
|
1243
|
-
"ollama.endpoint.attempt",
|
|
1244
|
-
{
|
|
1245
|
-
[ATTR_OLLAMA_ENDPOINT_NAME]: endpoint.name,
|
|
1246
|
-
[ATTR_OLLAMA_ENDPOINT_ATTEMPT]: attemptIndex
|
|
1247
|
-
},
|
|
1248
|
-
() => withRetry(() => operation(http, timeout.signal), this.retryConfig)
|
|
1460
|
+
for (; ; ) {
|
|
1461
|
+
const allCandidates = this.registry.candidates(options?.model);
|
|
1462
|
+
if (allCandidates.length === 0 && options?.model !== void 0 && this.registry.hasModelScopedEndpoints()) {
|
|
1463
|
+
const configured = this.registry.list().flatMap((ep) => ep.models ?? []).filter((m, i, arr) => arr.indexOf(m) === i);
|
|
1464
|
+
throw new OllamaModelRoutingError(
|
|
1465
|
+
`No configured endpoint is authorized for model "${options.model}". ` + (configured.length > 0 ? `Endpoints are scoped to: ${configured.join(", ")}.` : "No endpoint declares a `models` allow-list that includes it.") + " Add or widen an `OllamaEndpoint.models` entry to route this model.",
|
|
1466
|
+
{ model: options.model, availableModels: configured }
|
|
1249
1467
|
);
|
|
1250
|
-
this.registry.reportSuccess(endpoint.name);
|
|
1251
|
-
return result;
|
|
1252
|
-
} catch (err) {
|
|
1253
|
-
const error = err instanceof Error ? err : new Error(String(err));
|
|
1254
|
-
lastError = error;
|
|
1255
|
-
this.registry.reportFailure(endpoint.name);
|
|
1256
|
-
this.logger.warn(`Failed on "${endpoint.name}": ${error.message}`);
|
|
1257
|
-
if (!(error instanceof OllamaClientError && this.failoverCodes.has(error.code)))
|
|
1258
|
-
throw error;
|
|
1259
1468
|
}
|
|
1469
|
+
const candidates = options?.singleEndpoint ? allCandidates.slice(0, 1) : allCandidates;
|
|
1470
|
+
const runnable = this.registry.filterWithCapacity(candidates);
|
|
1471
|
+
if (runnable.length === 0 && candidates.length > 0) {
|
|
1472
|
+
await this.registry.waitForCapacity(
|
|
1473
|
+
candidates.map((c) => c.name),
|
|
1474
|
+
timeout.signal
|
|
1475
|
+
);
|
|
1476
|
+
continue;
|
|
1477
|
+
}
|
|
1478
|
+
for (const [attemptIndex, endpoint] of runnable.entries()) {
|
|
1479
|
+
this.logger.debug(`Executing on endpoint "${endpoint.name}" (${endpoint.baseUrl})`);
|
|
1480
|
+
const http = new HttpClient({
|
|
1481
|
+
baseUrl: endpoint.baseUrl,
|
|
1482
|
+
...endpoint.apiKey !== void 0 ? { apiKey: endpoint.apiKey } : {},
|
|
1483
|
+
...endpoint.headers !== void 0 ? { headers: endpoint.headers } : {},
|
|
1484
|
+
fetch: this.fetchImpl
|
|
1485
|
+
});
|
|
1486
|
+
this.registry.acquire(endpoint.name);
|
|
1487
|
+
let holdPromise;
|
|
1488
|
+
try {
|
|
1489
|
+
const result = await withSpan(
|
|
1490
|
+
"ollama.endpoint.attempt",
|
|
1491
|
+
{
|
|
1492
|
+
[ATTR_OLLAMA_ENDPOINT_NAME]: endpoint.name,
|
|
1493
|
+
[ATTR_OLLAMA_ENDPOINT_ATTEMPT]: attemptIndex
|
|
1494
|
+
},
|
|
1495
|
+
() => withRetry(() => operation(http, timeout.signal), this.retryConfig)
|
|
1496
|
+
);
|
|
1497
|
+
this.registry.reportSuccess(endpoint.name);
|
|
1498
|
+
if (options?.holdUntil) holdPromise = options.holdUntil(result);
|
|
1499
|
+
return result;
|
|
1500
|
+
} catch (err) {
|
|
1501
|
+
const error = err instanceof Error ? err : new Error(String(err));
|
|
1502
|
+
lastError = error;
|
|
1503
|
+
this.registry.reportFailure(endpoint.name);
|
|
1504
|
+
this.logger.warn(`Failed on "${endpoint.name}": ${error.message}`);
|
|
1505
|
+
if (!(error instanceof OllamaClientError && this.failoverCodes.has(error.code)))
|
|
1506
|
+
throw error;
|
|
1507
|
+
} finally {
|
|
1508
|
+
if (holdPromise) {
|
|
1509
|
+
void holdPromise.catch(() => void 0).finally(() => this.registry.release(endpoint.name));
|
|
1510
|
+
} else {
|
|
1511
|
+
this.registry.release(endpoint.name);
|
|
1512
|
+
}
|
|
1513
|
+
}
|
|
1514
|
+
}
|
|
1515
|
+
throw lastError ?? new Error("No healthy Ollama endpoints available");
|
|
1260
1516
|
}
|
|
1261
|
-
throw lastError ?? new Error("No healthy Ollama endpoints available");
|
|
1262
1517
|
} finally {
|
|
1263
1518
|
timeout.cancel();
|
|
1264
1519
|
}
|
|
1265
1520
|
}
|
|
1266
1521
|
async chat(req) {
|
|
1522
|
+
const messages = await withEncodedMessageImages(req.messages);
|
|
1267
1523
|
if (req.stream) {
|
|
1268
|
-
return this.executeWithFailover(
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1524
|
+
return this.executeWithFailover(
|
|
1525
|
+
async (http, signal) => {
|
|
1526
|
+
if (req.format !== void 0)
|
|
1527
|
+
this.assertStructuredOutputSupported(http.baseUrl, req.model);
|
|
1528
|
+
const stream = await http.requestStream({
|
|
1529
|
+
path: "/api/chat",
|
|
1530
|
+
body: { ...req, messages, stream: true },
|
|
1531
|
+
signal
|
|
1532
|
+
});
|
|
1533
|
+
return normalizeChatStream(stream);
|
|
1534
|
+
},
|
|
1535
|
+
{
|
|
1536
|
+
signal: req.signal,
|
|
1537
|
+
timeoutMs: req.timeoutMs,
|
|
1538
|
+
model: req.model,
|
|
1539
|
+
// The endpoint's concurrency slot must stay held for as long as the stream is
|
|
1540
|
+
// actually being consumed, not just until the initial response/headers arrive
|
|
1541
|
+
// — see `executeWithFailover`'s `holdUntil` doc.
|
|
1542
|
+
holdUntil: (stream) => stream.finalResult
|
|
1543
|
+
}
|
|
1544
|
+
);
|
|
1277
1545
|
}
|
|
1278
1546
|
return withSpan(
|
|
1279
1547
|
`chat ${req.model}`,
|
|
@@ -1288,7 +1556,7 @@ var OllamaClient = class {
|
|
|
1288
1556
|
this.assertStructuredOutputSupported(http.baseUrl, req.model);
|
|
1289
1557
|
return http.request({
|
|
1290
1558
|
path: "/api/chat",
|
|
1291
|
-
body: { ...req, stream: false },
|
|
1559
|
+
body: { ...req, messages, stream: false },
|
|
1292
1560
|
signal
|
|
1293
1561
|
});
|
|
1294
1562
|
}, req);
|
|
@@ -1315,34 +1583,44 @@ var OllamaClient = class {
|
|
|
1315
1583
|
return parseStructuredOutput(res.message.content, schema);
|
|
1316
1584
|
}
|
|
1317
1585
|
async generate(req) {
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1586
|
+
const encodedReq = await withEncodedImages(req);
|
|
1587
|
+
if (encodedReq.stream) {
|
|
1588
|
+
return this.executeWithFailover(
|
|
1589
|
+
async (http, signal) => {
|
|
1590
|
+
if (encodedReq.format !== void 0)
|
|
1591
|
+
this.assertStructuredOutputSupported(http.baseUrl, encodedReq.model);
|
|
1592
|
+
const stream = await http.requestStream({
|
|
1593
|
+
path: "/api/generate",
|
|
1594
|
+
body: { ...encodedReq, stream: true },
|
|
1595
|
+
signal
|
|
1596
|
+
});
|
|
1597
|
+
return normalizeGenerateStream(stream);
|
|
1598
|
+
},
|
|
1599
|
+
{
|
|
1600
|
+
signal: encodedReq.signal,
|
|
1601
|
+
timeoutMs: encodedReq.timeoutMs,
|
|
1602
|
+
model: encodedReq.model,
|
|
1603
|
+
holdUntil: (stream) => stream.finalResult
|
|
1604
|
+
}
|
|
1605
|
+
);
|
|
1328
1606
|
}
|
|
1329
1607
|
return withSpan(
|
|
1330
|
-
`text_completion ${
|
|
1608
|
+
`text_completion ${encodedReq.model}`,
|
|
1331
1609
|
{
|
|
1332
1610
|
[ATTR_GEN_AI_SYSTEM]: GEN_AI_SYSTEM_OLLAMA,
|
|
1333
1611
|
[ATTR_GEN_AI_OPERATION_NAME]: "text_completion",
|
|
1334
|
-
[ATTR_GEN_AI_REQUEST_MODEL]:
|
|
1612
|
+
[ATTR_GEN_AI_REQUEST_MODEL]: encodedReq.model
|
|
1335
1613
|
},
|
|
1336
1614
|
async (span) => {
|
|
1337
1615
|
const res = await this.executeWithFailover((http, signal) => {
|
|
1338
|
-
if (
|
|
1339
|
-
this.assertStructuredOutputSupported(http.baseUrl,
|
|
1616
|
+
if (encodedReq.format !== void 0)
|
|
1617
|
+
this.assertStructuredOutputSupported(http.baseUrl, encodedReq.model);
|
|
1340
1618
|
return http.request({
|
|
1341
1619
|
path: "/api/generate",
|
|
1342
|
-
body: { ...
|
|
1620
|
+
body: { ...encodedReq, stream: false },
|
|
1343
1621
|
signal
|
|
1344
1622
|
});
|
|
1345
|
-
},
|
|
1623
|
+
}, encodedReq);
|
|
1346
1624
|
span?.setAttributes({
|
|
1347
1625
|
[ATTR_GEN_AI_RESPONSE_MODEL]: res.model,
|
|
1348
1626
|
...res.prompt_eval_count !== void 0 ? { [ATTR_GEN_AI_USAGE_INPUT_TOKENS]: res.prompt_eval_count } : {},
|
|
@@ -1398,22 +1676,60 @@ var OllamaClient = class {
|
|
|
1398
1676
|
createBlob = (digest, data) => this.models.createBlob(digest, data);
|
|
1399
1677
|
checkBlob = (digest) => this.models.checkBlob(digest);
|
|
1400
1678
|
// --- Web Endpoints ---
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1679
|
+
/**
|
|
1680
|
+
* Ollama's hosted web search tool (`POST https://ollama.com/api/web_search`) — a fixed
|
|
1681
|
+
* Ollama Cloud service, unrelated to `config.endpoints`/`baseUrl` and not proxied
|
|
1682
|
+
* through a local server. Requires an Ollama account API key: pass `apiKey` to the
|
|
1683
|
+
* client constructor or set `OLLAMA_API_KEY`. Applies the same default `timeoutMs` and
|
|
1684
|
+
* retry policy as inference calls, but never cross-endpoint-fails-over — there is only
|
|
1685
|
+
* ever the one cloud endpoint to call.
|
|
1686
|
+
*/
|
|
1687
|
+
async webSearch(req) {
|
|
1688
|
+
const { count, max_results, ...rest } = req;
|
|
1689
|
+
const res = await this.executeCloudRequest(
|
|
1690
|
+
(http, signal) => http.request({
|
|
1691
|
+
path: "/api/web_search",
|
|
1692
|
+
body: { ...rest, max_results: max_results ?? count },
|
|
1693
|
+
signal
|
|
1694
|
+
}),
|
|
1404
1695
|
req
|
|
1405
1696
|
);
|
|
1697
|
+
return { results: res.results.map((r) => ({ ...r, snippet: r.content })) };
|
|
1406
1698
|
}
|
|
1699
|
+
/**
|
|
1700
|
+
* Ollama's hosted web fetch tool (`POST https://ollama.com/api/web_fetch`) — see
|
|
1701
|
+
* {@link OllamaClient.webSearch} for the cloud-endpoint/auth/timeout/retry behavior,
|
|
1702
|
+
* which applies identically here.
|
|
1703
|
+
*/
|
|
1407
1704
|
webFetch(req) {
|
|
1408
|
-
return this.
|
|
1409
|
-
(http, signal) => http.request({ path: "/api/
|
|
1705
|
+
return this.executeCloudRequest(
|
|
1706
|
+
(http, signal) => http.request({ path: "/api/web_fetch", body: req, signal }),
|
|
1410
1707
|
req
|
|
1411
1708
|
);
|
|
1412
1709
|
}
|
|
1710
|
+
/**
|
|
1711
|
+
* Runs `operation` against the fixed Ollama Cloud host with this client's default
|
|
1712
|
+
* timeout and retry policy — the same primitives `executeWithFailover` uses, minus the
|
|
1713
|
+
* multi-candidate loop, since `webSearch`/`webFetch` only ever have the one endpoint.
|
|
1714
|
+
*/
|
|
1715
|
+
async executeCloudRequest(operation, options) {
|
|
1716
|
+
const timeout = createTimeoutSignal(options.timeoutMs ?? this.timeoutMs, options.signal);
|
|
1717
|
+
try {
|
|
1718
|
+
const http = new HttpClient({
|
|
1719
|
+
baseUrl: OLLAMA_CLOUD_BASE_URL,
|
|
1720
|
+
apiKey: this.cloudApiKey,
|
|
1721
|
+
fetch: this.fetchImpl
|
|
1722
|
+
});
|
|
1723
|
+
return await withRetry(() => operation(http, timeout.signal), this.retryConfig);
|
|
1724
|
+
} finally {
|
|
1725
|
+
timeout.cancel();
|
|
1726
|
+
}
|
|
1727
|
+
}
|
|
1413
1728
|
// --- Capabilities & Health ---
|
|
1414
1729
|
capabilities(model) {
|
|
1415
1730
|
return this.executeWithFailover((http) => detectModelCapabilities(http, model), {
|
|
1416
|
-
singleEndpoint: true
|
|
1731
|
+
singleEndpoint: true,
|
|
1732
|
+
model
|
|
1417
1733
|
});
|
|
1418
1734
|
}
|
|
1419
1735
|
runtimeMode() {
|
|
@@ -1531,6 +1847,117 @@ function toResponse(stream, init = {}) {
|
|
|
1531
1847
|
return new Response(body, { ...init, headers });
|
|
1532
1848
|
}
|
|
1533
1849
|
|
|
1850
|
+
// src/quota.ts
|
|
1851
|
+
var QuotaManager = class {
|
|
1852
|
+
now;
|
|
1853
|
+
windows;
|
|
1854
|
+
constructor(options) {
|
|
1855
|
+
if (options.windows.length === 0) {
|
|
1856
|
+
throw new Error("QuotaManager requires at least one window configuration.");
|
|
1857
|
+
}
|
|
1858
|
+
this.now = options.now ?? Date.now;
|
|
1859
|
+
this.windows = options.windows.map((config) => ({
|
|
1860
|
+
config,
|
|
1861
|
+
tokensUsed: 0,
|
|
1862
|
+
requestsMade: 0,
|
|
1863
|
+
windowStartedAt: this.now()
|
|
1864
|
+
}));
|
|
1865
|
+
}
|
|
1866
|
+
rollover(state) {
|
|
1867
|
+
if (this.now() - state.windowStartedAt >= state.config.windowMs) {
|
|
1868
|
+
state.tokensUsed = 0;
|
|
1869
|
+
state.requestsMade = 0;
|
|
1870
|
+
state.windowStartedAt = this.now();
|
|
1871
|
+
}
|
|
1872
|
+
}
|
|
1873
|
+
/** Current usage, budgets, and reset time for every configured window. */
|
|
1874
|
+
status() {
|
|
1875
|
+
return this.windows.map((state) => {
|
|
1876
|
+
this.rollover(state);
|
|
1877
|
+
const { config } = state;
|
|
1878
|
+
return {
|
|
1879
|
+
...config,
|
|
1880
|
+
tokensUsed: state.tokensUsed,
|
|
1881
|
+
requestsMade: state.requestsMade,
|
|
1882
|
+
windowStartedAt: state.windowStartedAt,
|
|
1883
|
+
windowResetAt: state.windowStartedAt + config.windowMs,
|
|
1884
|
+
...config.maxTokens !== void 0 ? { remainingTokens: Math.max(0, config.maxTokens - state.tokensUsed) } : {},
|
|
1885
|
+
...config.maxRequests !== void 0 ? { remainingRequests: Math.max(0, config.maxRequests - state.requestsMade) } : {}
|
|
1886
|
+
};
|
|
1887
|
+
});
|
|
1888
|
+
}
|
|
1889
|
+
/**
|
|
1890
|
+
* True if one more request — optionally estimated at `estimatedTokens` — would stay
|
|
1891
|
+
* within every configured window's budget. Read-only; does not record usage.
|
|
1892
|
+
*/
|
|
1893
|
+
canProceed(estimatedTokens = 0) {
|
|
1894
|
+
return this.windows.every((state) => {
|
|
1895
|
+
this.rollover(state);
|
|
1896
|
+
const { config } = state;
|
|
1897
|
+
if (config.maxRequests !== void 0 && state.requestsMade + 1 > config.maxRequests) {
|
|
1898
|
+
return false;
|
|
1899
|
+
}
|
|
1900
|
+
return !(config.maxTokens !== void 0 && state.tokensUsed + estimatedTokens > config.maxTokens);
|
|
1901
|
+
});
|
|
1902
|
+
}
|
|
1903
|
+
/**
|
|
1904
|
+
* Throws `OllamaQuotaExceededError` for the first window that `canProceed(estimatedTokens)`
|
|
1905
|
+
* would fail on; otherwise a no-op. Call before issuing a request.
|
|
1906
|
+
*/
|
|
1907
|
+
assertCanProceed(estimatedTokens = 0) {
|
|
1908
|
+
for (const state of this.windows) {
|
|
1909
|
+
this.rollover(state);
|
|
1910
|
+
const { config } = state;
|
|
1911
|
+
if (config.maxRequests !== void 0 && state.requestsMade + 1 > config.maxRequests) {
|
|
1912
|
+
throw new OllamaQuotaExceededError(
|
|
1913
|
+
`Client-side quota window "${config.id}" would exceed its request budget (${state.requestsMade}/${config.maxRequests} requests used).`,
|
|
1914
|
+
{ windowId: config.id, resetAt: state.windowStartedAt + config.windowMs }
|
|
1915
|
+
);
|
|
1916
|
+
}
|
|
1917
|
+
if (config.maxTokens !== void 0 && state.tokensUsed + estimatedTokens > config.maxTokens) {
|
|
1918
|
+
throw new OllamaQuotaExceededError(
|
|
1919
|
+
`Client-side quota window "${config.id}" would exceed its token budget (${state.tokensUsed}/${config.maxTokens} tokens used).`,
|
|
1920
|
+
{ windowId: config.id, resetAt: state.windowStartedAt + config.windowMs }
|
|
1921
|
+
);
|
|
1922
|
+
}
|
|
1923
|
+
}
|
|
1924
|
+
}
|
|
1925
|
+
/** Records one request's actual usage — a raw API response or a pre-normalized `TokenUsage` — against every window. */
|
|
1926
|
+
recordUsage(usage) {
|
|
1927
|
+
const normalized = "totalTokens" in usage ? usage : extractUsage(usage);
|
|
1928
|
+
for (const state of this.windows) {
|
|
1929
|
+
this.rollover(state);
|
|
1930
|
+
state.tokensUsed += normalized.totalTokens;
|
|
1931
|
+
state.requestsMade += 1;
|
|
1932
|
+
}
|
|
1933
|
+
}
|
|
1934
|
+
/** Resets one window (by `id`) or, if omitted, all windows — zeroing usage and restarting their clocks. */
|
|
1935
|
+
reset(windowId) {
|
|
1936
|
+
for (const state of this.windows) {
|
|
1937
|
+
if (windowId === void 0 || state.config.id === windowId) {
|
|
1938
|
+
state.tokensUsed = 0;
|
|
1939
|
+
state.requestsMade = 0;
|
|
1940
|
+
state.windowStartedAt = this.now();
|
|
1941
|
+
}
|
|
1942
|
+
}
|
|
1943
|
+
}
|
|
1944
|
+
};
|
|
1945
|
+
function createOllamaCloudFreeTierQuota(budgets) {
|
|
1946
|
+
const windows = [];
|
|
1947
|
+
if (budgets.session !== void 0) {
|
|
1948
|
+
windows.push({ id: "session", windowMs: 5 * 60 * 60 * 1e3, ...budgets.session });
|
|
1949
|
+
}
|
|
1950
|
+
if (budgets.weekly !== void 0) {
|
|
1951
|
+
windows.push({ id: "weekly", windowMs: 7 * 24 * 60 * 60 * 1e3, ...budgets.weekly });
|
|
1952
|
+
}
|
|
1953
|
+
if (windows.length === 0) {
|
|
1954
|
+
throw new Error(
|
|
1955
|
+
"createOllamaCloudFreeTierQuota requires at least a `session` or `weekly` budget."
|
|
1956
|
+
);
|
|
1957
|
+
}
|
|
1958
|
+
return new QuotaManager({ windows, ...budgets.now !== void 0 ? { now: budgets.now } : {} });
|
|
1959
|
+
}
|
|
1960
|
+
|
|
1534
1961
|
// src/tools/define-tool.ts
|
|
1535
1962
|
function defineTool(options) {
|
|
1536
1963
|
const jsonSchema = zodToJsonSchema(options.schema);
|
|
@@ -1997,6 +2424,7 @@ exports.ModelsClient = ModelsClient;
|
|
|
1997
2424
|
exports.NANOS_PER_MS = NANOS_PER_MS;
|
|
1998
2425
|
exports.NANOS_PER_SECOND = NANOS_PER_SECOND;
|
|
1999
2426
|
exports.NOOP_LOGGER = NOOP_LOGGER;
|
|
2427
|
+
exports.OLLAMA_CLOUD_BASE_URL = OLLAMA_CLOUD_BASE_URL;
|
|
2000
2428
|
exports.OllamaAbortError = OllamaAbortError;
|
|
2001
2429
|
exports.OllamaAgentMaxIterationsError = OllamaAgentMaxIterationsError;
|
|
2002
2430
|
exports.OllamaAuthError = OllamaAuthError;
|
|
@@ -2004,8 +2432,10 @@ exports.OllamaClient = OllamaClient;
|
|
|
2004
2432
|
exports.OllamaClientError = OllamaClientError;
|
|
2005
2433
|
exports.OllamaGenericClientError = OllamaGenericClientError;
|
|
2006
2434
|
exports.OllamaMcpError = OllamaMcpError;
|
|
2435
|
+
exports.OllamaModelRoutingError = OllamaModelRoutingError;
|
|
2007
2436
|
exports.OllamaNetworkError = OllamaNetworkError;
|
|
2008
2437
|
exports.OllamaNotFoundError = OllamaNotFoundError;
|
|
2438
|
+
exports.OllamaQuotaExceededError = OllamaQuotaExceededError;
|
|
2009
2439
|
exports.OllamaRateLimitError = OllamaRateLimitError;
|
|
2010
2440
|
exports.OllamaServerError = OllamaServerError;
|
|
2011
2441
|
exports.OllamaSkillInvalidError = OllamaSkillInvalidError;
|
|
@@ -2016,6 +2446,7 @@ exports.OllamaToolTimeoutError = OllamaToolTimeoutError;
|
|
|
2016
2446
|
exports.OllamaToolValidationError = OllamaToolValidationError;
|
|
2017
2447
|
exports.OllamaUnsupportedCapabilityError = OllamaUnsupportedCapabilityError;
|
|
2018
2448
|
exports.OpenAICompatClient = OpenAICompatClient;
|
|
2449
|
+
exports.QuotaManager = QuotaManager;
|
|
2019
2450
|
exports.ToolRegistry = ToolRegistry;
|
|
2020
2451
|
exports.addSpanEvent = addSpanEvent;
|
|
2021
2452
|
exports.applySkill = applySkill;
|
|
@@ -2023,9 +2454,11 @@ exports.calculateBackoff = calculateBackoff;
|
|
|
2023
2454
|
exports.checkEndpointHealth = checkEndpointHealth;
|
|
2024
2455
|
exports.composeMiddleware = composeMiddleware;
|
|
2025
2456
|
exports.createConsoleLogger = createConsoleLogger;
|
|
2457
|
+
exports.createOllamaCloudFreeTierQuota = createOllamaCloudFreeTierQuota;
|
|
2026
2458
|
exports.createTimeoutSignal = createTimeoutSignal;
|
|
2027
2459
|
exports.defineTool = defineTool;
|
|
2028
2460
|
exports.detectModelCapabilities = detectModelCapabilities;
|
|
2461
|
+
exports.encodeImage = encodeImage;
|
|
2029
2462
|
exports.extractUsage = extractUsage;
|
|
2030
2463
|
exports.inferRuntimeMode = inferRuntimeMode;
|
|
2031
2464
|
exports.listAvailableModels = listAvailableModels;
|
|
@@ -2038,6 +2471,7 @@ exports.parseFrontmatter = parseFrontmatter;
|
|
|
2038
2471
|
exports.parseNdjsonStream = parseNdjsonStream;
|
|
2039
2472
|
exports.parseStructuredOutput = parseStructuredOutput;
|
|
2040
2473
|
exports.registerMcpTools = registerMcpTools;
|
|
2474
|
+
exports.resolveCredentialEndpoints = resolveCredentialEndpoints;
|
|
2041
2475
|
exports.setSpanError = setSpanError;
|
|
2042
2476
|
exports.toDataStream = toDataStream;
|
|
2043
2477
|
exports.toResponse = toResponse;
|