@octocodeai/freellama-mcp-server 0.1.0 → 0.6.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/CONFIGURATION.md +108 -0
- package/DEVELOPING.md +109 -0
- package/LICENSE-APACHE +202 -0
- package/LICENSE-MIT +18 -0
- package/README.md +261 -243
- package/assets/logo.jpg +0 -0
- package/dist/index.js +2305 -1332
- package/dist/research/bash-agent.js +2435 -0
- package/dist/research/octocode-agent.js +1639 -0
- package/docs/ARCHITECTURE.md +294 -87
- package/docs/CLI.md +171 -53
- package/docs/CPU_GPU_ROUTING.md +69 -23
- package/docs/MODEL_SELECTION.md +126 -3
- package/docs/MONITORING.md +293 -0
- package/docs/PRODUCTION.md +53 -25
- package/docs/README.md +36 -0
- package/docs/SCOPES_AND_WARMING.md +225 -0
- package/docs/TESTING.md +113 -8
- package/native/index.d.ts +186 -150
- package/native/index.js +3 -3
- package/package.json +15 -11
- package/adapters/agent_context.py +0 -663
- package/adapters/agent_transport.py +0 -84
- package/adapters/bash_agent.py +0 -370
- package/adapters/octocode_agent.py +0 -394
- package/docs/ADR_RESOURCE_AWARE_BACKEND_ROUTING.md +0 -55
- package/docs/ECONOMICS.md +0 -105
- package/docs/INDEX.md +0 -15
- package/docs/OLLAMA_SIDECAR.md +0 -76
- package/docs/OLLAMA_SYSTEM_OPTIMIZATION.md +0 -254
- package/docs/PRODUCT_POSITIONING.md +0 -291
package/dist/index.js
CHANGED
|
@@ -3109,6 +3109,7 @@ var require_utils = __commonJS({
|
|
|
3109
3109
|
"use strict";
|
|
3110
3110
|
var isUUID = RegExp.prototype.test.bind(/^[\da-f]{8}-[\da-f]{4}-[\da-f]{4}-[\da-f]{4}-[\da-f]{12}$/iu);
|
|
3111
3111
|
var isIPv4 = RegExp.prototype.test.bind(/^(?:(?:25[0-5]|2[0-4]\d|1\d{2}|[1-9]\d|\d)\.){3}(?:25[0-5]|2[0-4]\d|1\d{2}|[1-9]\d|\d)$/u);
|
|
3112
|
+
var isPort = RegExp.prototype.test.bind(/^\d*$/u);
|
|
3112
3113
|
var isHexPair = RegExp.prototype.test.bind(/^[\da-f]{2}$/iu);
|
|
3113
3114
|
var isUnreserved = RegExp.prototype.test.bind(/^[\da-z\-._~]$/iu);
|
|
3114
3115
|
var isPathCharacter = RegExp.prototype.test.bind(/^[A-Za-z0-9\-._~!$&'()*+,;=:@/]$/u);
|
|
@@ -3261,8 +3262,8 @@ var require_utils = __commonJS({
|
|
|
3261
3262
|
}
|
|
3262
3263
|
return ind;
|
|
3263
3264
|
}
|
|
3264
|
-
function removeDotSegments(
|
|
3265
|
-
let input =
|
|
3265
|
+
function removeDotSegments(path6) {
|
|
3266
|
+
let input = path6;
|
|
3266
3267
|
const output = [];
|
|
3267
3268
|
let nextSlash = -1;
|
|
3268
3269
|
let len = 0;
|
|
@@ -3574,8 +3575,12 @@ var require_utils = __commonJS({
|
|
|
3574
3575
|
uriTokens.push(host);
|
|
3575
3576
|
}
|
|
3576
3577
|
if (typeof component.port === "number" || typeof component.port === "string") {
|
|
3578
|
+
const port = String(component.port);
|
|
3579
|
+
if (!isPort(port)) {
|
|
3580
|
+
throw new TypeError("URI port is malformed.");
|
|
3581
|
+
}
|
|
3577
3582
|
uriTokens.push(":");
|
|
3578
|
-
uriTokens.push(
|
|
3583
|
+
uriTokens.push(port);
|
|
3579
3584
|
}
|
|
3580
3585
|
return uriTokens.length ? uriTokens.join("") : void 0;
|
|
3581
3586
|
}
|
|
@@ -3667,8 +3672,8 @@ var require_schemes = __commonJS({
|
|
|
3667
3672
|
}
|
|
3668
3673
|
if (wsComponent.resourceName) {
|
|
3669
3674
|
const queryIndex = wsComponent.resourceName.indexOf("?");
|
|
3670
|
-
const
|
|
3671
|
-
wsComponent.path =
|
|
3675
|
+
const path6 = queryIndex === -1 ? wsComponent.resourceName : wsComponent.resourceName.slice(0, queryIndex);
|
|
3676
|
+
wsComponent.path = path6 && path6 !== "/" ? path6 : void 0;
|
|
3672
3677
|
wsComponent.query = queryIndex === -1 ? void 0 : wsComponent.resourceName.slice(queryIndex + 1);
|
|
3673
3678
|
wsComponent.resourceName = void 0;
|
|
3674
3679
|
}
|
|
@@ -3832,7 +3837,7 @@ var require_fast_uri = __commonJS({
|
|
|
3832
3837
|
normalizeString(uri, options);
|
|
3833
3838
|
} else if (typeof uri === "object") {
|
|
3834
3839
|
uri = /** @type {T} */
|
|
3835
|
-
parse3(
|
|
3840
|
+
parse3(serialize(uri, options), options);
|
|
3836
3841
|
}
|
|
3837
3842
|
return uri;
|
|
3838
3843
|
}
|
|
@@ -3867,13 +3872,13 @@ var require_fast_uri = __commonJS({
|
|
|
3867
3872
|
throw new Error(resolved.error);
|
|
3868
3873
|
}
|
|
3869
3874
|
schemelessOptions.skipEscape = true;
|
|
3870
|
-
return
|
|
3875
|
+
return serialize(resolved, schemelessOptions);
|
|
3871
3876
|
}
|
|
3872
3877
|
function resolveComponent(base, relative, options, skipNormalization) {
|
|
3873
3878
|
const target = {};
|
|
3874
3879
|
if (!skipNormalization) {
|
|
3875
|
-
base = parse3(
|
|
3876
|
-
relative = parse3(
|
|
3880
|
+
base = parse3(serialize(base, options), options);
|
|
3881
|
+
relative = parse3(serialize(relative, options), options);
|
|
3877
3882
|
}
|
|
3878
3883
|
options = options || {};
|
|
3879
3884
|
if (!options.tolerant && relative.scheme) {
|
|
@@ -3927,7 +3932,7 @@ var require_fast_uri = __commonJS({
|
|
|
3927
3932
|
const normalizedB = normalizeComparableURI(uriB, options);
|
|
3928
3933
|
return normalizedA !== void 0 && normalizedB !== void 0 && normalizedA === normalizedB;
|
|
3929
3934
|
}
|
|
3930
|
-
function
|
|
3935
|
+
function serialize(cmpts, opts) {
|
|
3931
3936
|
const component = {
|
|
3932
3937
|
host: cmpts.host,
|
|
3933
3938
|
scheme: cmpts.scheme,
|
|
@@ -4018,12 +4023,15 @@ var require_fast_uri = __commonJS({
|
|
|
4018
4023
|
}
|
|
4019
4024
|
return false;
|
|
4020
4025
|
}
|
|
4026
|
+
function isIPLiteral(host) {
|
|
4027
|
+
return host[0] === "[" && host[host.length - 1] === "]";
|
|
4028
|
+
}
|
|
4021
4029
|
function hasMalformedComponentPercentEncoding(matches) {
|
|
4022
4030
|
const host = matches[4];
|
|
4023
|
-
return hasMalformedPercentEncoding(matches[3]) || host !== void 0 && !(host
|
|
4031
|
+
return hasMalformedPercentEncoding(matches[3]) || host !== void 0 && !isIPLiteral(host) && hasMalformedPercentEncoding(host) || hasMalformedPercentEncoding(matches[6]) || hasMalformedPercentEncoding(matches[7]) || hasMalformedPercentEncoding(matches[8]);
|
|
4024
4032
|
}
|
|
4025
4033
|
function canonicalizeHost(parsed, options, schemeHandler, isIP) {
|
|
4026
|
-
if (!options.unicodeSupport && (!schemeHandler || !schemeHandler.unicodeSupport) && parsed.host && parsed.host
|
|
4034
|
+
if (!options.unicodeSupport && (!schemeHandler || !schemeHandler.unicodeSupport) && parsed.host && !isIPLiteral(parsed.host) && (options.domainHost || schemeHandler && schemeHandler.domainHost) && isIP === false && nonSimpleDomain(parsed.host)) {
|
|
4027
4035
|
try {
|
|
4028
4036
|
parsed.host = new URL("http://" + parsed.host).hostname;
|
|
4029
4037
|
} catch (e) {
|
|
@@ -4110,10 +4118,11 @@ var require_fast_uri = __commonJS({
|
|
|
4110
4118
|
if (parsed.host) {
|
|
4111
4119
|
const ipv4result = isIPv4(parsed.host);
|
|
4112
4120
|
if (ipv4result === false) {
|
|
4113
|
-
const bracketedIPLiteral = parsed.host
|
|
4121
|
+
const bracketedIPLiteral = isIPLiteral(parsed.host);
|
|
4122
|
+
const hasIPLiteralBracket = parsed.host.indexOf("[") !== -1 || parsed.host.indexOf("]") !== -1;
|
|
4114
4123
|
const ipv6result = normalizeIPv6(parsed.host);
|
|
4115
4124
|
isIP = ipv6result.isIPV6 || ipv6result.isIPVFuture === true;
|
|
4116
|
-
malformedIPLiteral =
|
|
4125
|
+
malformedIPLiteral = hasIPLiteralBracket && (!bracketedIPLiteral || ipv6result.error === true);
|
|
4117
4126
|
parsed.host = isIP ? ipv6result.host : ipv6result.host.toLowerCase();
|
|
4118
4127
|
if (malformedIPLiteral) {
|
|
4119
4128
|
parsed.error = parsed.error || "URI host is malformed.";
|
|
@@ -4136,14 +4145,17 @@ var require_fast_uri = __commonJS({
|
|
|
4136
4145
|
parsed.error = parsed.error || "URI is not a " + options.reference + " reference.";
|
|
4137
4146
|
}
|
|
4138
4147
|
const schemeHandler = getSchemeHandler(options.scheme || parsed.scheme);
|
|
4139
|
-
|
|
4140
|
-
|
|
4141
|
-
|
|
4142
|
-
|
|
4143
|
-
|
|
4144
|
-
|
|
4145
|
-
|
|
4148
|
+
if (!malformedIPLiteral) {
|
|
4149
|
+
malformedHost = canonicalizeHost(parsed, options, schemeHandler, isIP);
|
|
4150
|
+
}
|
|
4151
|
+
if (uri.indexOf("%") !== -1 && parsed.host !== void 0 && !malformedIPLiteral) {
|
|
4152
|
+
let host = isIP ? parsed.host : normalizePercentEncoding(parsed.host, true);
|
|
4153
|
+
if (!isIP) {
|
|
4154
|
+
host = normalizePercentEncoding(host.toLowerCase());
|
|
4146
4155
|
}
|
|
4156
|
+
parsed.host = reescapeHostDelimiters(host, isIP);
|
|
4157
|
+
}
|
|
4158
|
+
if (!schemeHandler || schemeHandler && !schemeHandler.skipNormalize) {
|
|
4147
4159
|
if (parsed.path) {
|
|
4148
4160
|
parsed.path = normalizePathEncoding(parsed.path);
|
|
4149
4161
|
}
|
|
@@ -4174,7 +4186,7 @@ var require_fast_uri = __commonJS({
|
|
|
4174
4186
|
function normalizeStringWithStatus(uri, opts) {
|
|
4175
4187
|
const { parsed, malformedAuthorityOrPort, malformedPercentEncoding, malformedSchemeSpecific, malformedHost, malformedScheme } = parseWithStatus(uri, opts);
|
|
4176
4188
|
return {
|
|
4177
|
-
normalized: malformedAuthorityOrPort || malformedPercentEncoding || malformedSchemeSpecific || malformedHost || malformedScheme ? uri :
|
|
4189
|
+
normalized: malformedAuthorityOrPort || malformedPercentEncoding || malformedSchemeSpecific || malformedHost || malformedScheme ? uri : serialize(parsed, opts),
|
|
4178
4190
|
malformedAuthorityOrPort,
|
|
4179
4191
|
malformedPercentEncoding,
|
|
4180
4192
|
malformedSchemeSpecific,
|
|
@@ -4188,7 +4200,7 @@ var require_fast_uri = __commonJS({
|
|
|
4188
4200
|
}
|
|
4189
4201
|
let value;
|
|
4190
4202
|
try {
|
|
4191
|
-
value = typeof uri === "string" ? uri :
|
|
4203
|
+
value = typeof uri === "string" ? uri : serialize(uri, opts);
|
|
4192
4204
|
} catch {
|
|
4193
4205
|
return void 0;
|
|
4194
4206
|
}
|
|
@@ -4201,7 +4213,7 @@ var require_fast_uri = __commonJS({
|
|
|
4201
4213
|
resolve,
|
|
4202
4214
|
resolveComponent,
|
|
4203
4215
|
equal,
|
|
4204
|
-
serialize
|
|
4216
|
+
serialize,
|
|
4205
4217
|
parse: parse3
|
|
4206
4218
|
};
|
|
4207
4219
|
module.exports = fastUri;
|
|
@@ -7187,16 +7199,6 @@ var require_dist = __commonJS({
|
|
|
7187
7199
|
}
|
|
7188
7200
|
});
|
|
7189
7201
|
|
|
7190
|
-
// src/index.ts
|
|
7191
|
-
import { execFile } from "node:child_process";
|
|
7192
|
-
import { existsSync as existsSync4, readdirSync } from "node:fs";
|
|
7193
|
-
import { mkdtemp, readFile, rm, writeFile } from "node:fs/promises";
|
|
7194
|
-
import { createHash } from "node:crypto";
|
|
7195
|
-
import { tmpdir } from "node:os";
|
|
7196
|
-
import path4 from "node:path";
|
|
7197
|
-
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
7198
|
-
import { promisify } from "node:util";
|
|
7199
|
-
|
|
7200
7202
|
// ../../node_modules/zod/v3/external.js
|
|
7201
7203
|
var external_exports = {};
|
|
7202
7204
|
__export(external_exports, {
|
|
@@ -7675,8 +7677,8 @@ function getErrorMap() {
|
|
|
7675
7677
|
|
|
7676
7678
|
// ../../node_modules/zod/v3/helpers/parseUtil.js
|
|
7677
7679
|
var makeIssue = (params) => {
|
|
7678
|
-
const { data, path:
|
|
7679
|
-
const fullPath = [...
|
|
7680
|
+
const { data, path: path6, errorMaps, issueData } = params;
|
|
7681
|
+
const fullPath = [...path6, ...issueData.path || []];
|
|
7680
7682
|
const fullIssue = {
|
|
7681
7683
|
...issueData,
|
|
7682
7684
|
path: fullPath
|
|
@@ -7688,15 +7690,15 @@ var makeIssue = (params) => {
|
|
|
7688
7690
|
message: issueData.message
|
|
7689
7691
|
};
|
|
7690
7692
|
}
|
|
7691
|
-
let
|
|
7693
|
+
let errorMessage2 = "";
|
|
7692
7694
|
const maps = errorMaps.filter((m) => !!m).slice().reverse();
|
|
7693
7695
|
for (const map of maps) {
|
|
7694
|
-
|
|
7696
|
+
errorMessage2 = map(fullIssue, { data, defaultError: errorMessage2 }).message;
|
|
7695
7697
|
}
|
|
7696
7698
|
return {
|
|
7697
7699
|
...issueData,
|
|
7698
7700
|
path: fullPath,
|
|
7699
|
-
message:
|
|
7701
|
+
message: errorMessage2
|
|
7700
7702
|
};
|
|
7701
7703
|
};
|
|
7702
7704
|
var EMPTY_PATH = [];
|
|
@@ -7731,18 +7733,18 @@ var ParseStatus = class _ParseStatus {
|
|
|
7731
7733
|
if (this.value !== "aborted")
|
|
7732
7734
|
this.value = "aborted";
|
|
7733
7735
|
}
|
|
7734
|
-
static mergeArray(
|
|
7736
|
+
static mergeArray(status2, results) {
|
|
7735
7737
|
const arrayValue = [];
|
|
7736
7738
|
for (const s of results) {
|
|
7737
7739
|
if (s.status === "aborted")
|
|
7738
7740
|
return INVALID;
|
|
7739
7741
|
if (s.status === "dirty")
|
|
7740
|
-
|
|
7742
|
+
status2.dirty();
|
|
7741
7743
|
arrayValue.push(s.value);
|
|
7742
7744
|
}
|
|
7743
|
-
return { status:
|
|
7745
|
+
return { status: status2.value, value: arrayValue };
|
|
7744
7746
|
}
|
|
7745
|
-
static async mergeObjectAsync(
|
|
7747
|
+
static async mergeObjectAsync(status2, pairs) {
|
|
7746
7748
|
const syncPairs = [];
|
|
7747
7749
|
for (const pair of pairs) {
|
|
7748
7750
|
const key = await pair.key;
|
|
@@ -7752,9 +7754,9 @@ var ParseStatus = class _ParseStatus {
|
|
|
7752
7754
|
value
|
|
7753
7755
|
});
|
|
7754
7756
|
}
|
|
7755
|
-
return _ParseStatus.mergeObjectSync(
|
|
7757
|
+
return _ParseStatus.mergeObjectSync(status2, syncPairs);
|
|
7756
7758
|
}
|
|
7757
|
-
static mergeObjectSync(
|
|
7759
|
+
static mergeObjectSync(status2, pairs) {
|
|
7758
7760
|
const finalObject = {};
|
|
7759
7761
|
for (const pair of pairs) {
|
|
7760
7762
|
const { key, value } = pair;
|
|
@@ -7763,14 +7765,14 @@ var ParseStatus = class _ParseStatus {
|
|
|
7763
7765
|
if (value.status === "aborted")
|
|
7764
7766
|
return INVALID;
|
|
7765
7767
|
if (key.status === "dirty")
|
|
7766
|
-
|
|
7768
|
+
status2.dirty();
|
|
7767
7769
|
if (value.status === "dirty")
|
|
7768
|
-
|
|
7770
|
+
status2.dirty();
|
|
7769
7771
|
if (key.value !== "__proto__" && (typeof value.value !== "undefined" || pair.alwaysSet)) {
|
|
7770
7772
|
finalObject[key.value] = value.value;
|
|
7771
7773
|
}
|
|
7772
7774
|
}
|
|
7773
|
-
return { status:
|
|
7775
|
+
return { status: status2.value, value: finalObject };
|
|
7774
7776
|
}
|
|
7775
7777
|
};
|
|
7776
7778
|
var INVALID = Object.freeze({
|
|
@@ -7792,11 +7794,11 @@ var errorUtil;
|
|
|
7792
7794
|
|
|
7793
7795
|
// ../../node_modules/zod/v3/types.js
|
|
7794
7796
|
var ParseInputLazyPath = class {
|
|
7795
|
-
constructor(parent, value,
|
|
7797
|
+
constructor(parent, value, path6, key) {
|
|
7796
7798
|
this._cachedPath = [];
|
|
7797
7799
|
this.parent = parent;
|
|
7798
7800
|
this.data = value;
|
|
7799
|
-
this._path =
|
|
7801
|
+
this._path = path6;
|
|
7800
7802
|
this._key = key;
|
|
7801
7803
|
}
|
|
7802
7804
|
get path() {
|
|
@@ -8230,7 +8232,7 @@ var ZodString = class _ZodString2 extends ZodType {
|
|
|
8230
8232
|
});
|
|
8231
8233
|
return INVALID;
|
|
8232
8234
|
}
|
|
8233
|
-
const
|
|
8235
|
+
const status2 = new ParseStatus();
|
|
8234
8236
|
let ctx = void 0;
|
|
8235
8237
|
for (const check2 of this._def.checks) {
|
|
8236
8238
|
if (check2.kind === "min") {
|
|
@@ -8244,7 +8246,7 @@ var ZodString = class _ZodString2 extends ZodType {
|
|
|
8244
8246
|
exact: false,
|
|
8245
8247
|
message: check2.message
|
|
8246
8248
|
});
|
|
8247
|
-
|
|
8249
|
+
status2.dirty();
|
|
8248
8250
|
}
|
|
8249
8251
|
} else if (check2.kind === "max") {
|
|
8250
8252
|
if (input.data.length > check2.value) {
|
|
@@ -8257,7 +8259,7 @@ var ZodString = class _ZodString2 extends ZodType {
|
|
|
8257
8259
|
exact: false,
|
|
8258
8260
|
message: check2.message
|
|
8259
8261
|
});
|
|
8260
|
-
|
|
8262
|
+
status2.dirty();
|
|
8261
8263
|
}
|
|
8262
8264
|
} else if (check2.kind === "length") {
|
|
8263
8265
|
const tooBig = input.data.length > check2.value;
|
|
@@ -8283,7 +8285,7 @@ var ZodString = class _ZodString2 extends ZodType {
|
|
|
8283
8285
|
message: check2.message
|
|
8284
8286
|
});
|
|
8285
8287
|
}
|
|
8286
|
-
|
|
8288
|
+
status2.dirty();
|
|
8287
8289
|
}
|
|
8288
8290
|
} else if (check2.kind === "email") {
|
|
8289
8291
|
if (!emailRegex.test(input.data)) {
|
|
@@ -8293,7 +8295,7 @@ var ZodString = class _ZodString2 extends ZodType {
|
|
|
8293
8295
|
code: ZodIssueCode.invalid_string,
|
|
8294
8296
|
message: check2.message
|
|
8295
8297
|
});
|
|
8296
|
-
|
|
8298
|
+
status2.dirty();
|
|
8297
8299
|
}
|
|
8298
8300
|
} else if (check2.kind === "emoji") {
|
|
8299
8301
|
if (!emojiRegex) {
|
|
@@ -8306,7 +8308,7 @@ var ZodString = class _ZodString2 extends ZodType {
|
|
|
8306
8308
|
code: ZodIssueCode.invalid_string,
|
|
8307
8309
|
message: check2.message
|
|
8308
8310
|
});
|
|
8309
|
-
|
|
8311
|
+
status2.dirty();
|
|
8310
8312
|
}
|
|
8311
8313
|
} else if (check2.kind === "uuid") {
|
|
8312
8314
|
if (!uuidRegex.test(input.data)) {
|
|
@@ -8316,7 +8318,7 @@ var ZodString = class _ZodString2 extends ZodType {
|
|
|
8316
8318
|
code: ZodIssueCode.invalid_string,
|
|
8317
8319
|
message: check2.message
|
|
8318
8320
|
});
|
|
8319
|
-
|
|
8321
|
+
status2.dirty();
|
|
8320
8322
|
}
|
|
8321
8323
|
} else if (check2.kind === "nanoid") {
|
|
8322
8324
|
if (!nanoidRegex.test(input.data)) {
|
|
@@ -8326,7 +8328,7 @@ var ZodString = class _ZodString2 extends ZodType {
|
|
|
8326
8328
|
code: ZodIssueCode.invalid_string,
|
|
8327
8329
|
message: check2.message
|
|
8328
8330
|
});
|
|
8329
|
-
|
|
8331
|
+
status2.dirty();
|
|
8330
8332
|
}
|
|
8331
8333
|
} else if (check2.kind === "cuid") {
|
|
8332
8334
|
if (!cuidRegex.test(input.data)) {
|
|
@@ -8336,7 +8338,7 @@ var ZodString = class _ZodString2 extends ZodType {
|
|
|
8336
8338
|
code: ZodIssueCode.invalid_string,
|
|
8337
8339
|
message: check2.message
|
|
8338
8340
|
});
|
|
8339
|
-
|
|
8341
|
+
status2.dirty();
|
|
8340
8342
|
}
|
|
8341
8343
|
} else if (check2.kind === "cuid2") {
|
|
8342
8344
|
if (!cuid2Regex.test(input.data)) {
|
|
@@ -8346,7 +8348,7 @@ var ZodString = class _ZodString2 extends ZodType {
|
|
|
8346
8348
|
code: ZodIssueCode.invalid_string,
|
|
8347
8349
|
message: check2.message
|
|
8348
8350
|
});
|
|
8349
|
-
|
|
8351
|
+
status2.dirty();
|
|
8350
8352
|
}
|
|
8351
8353
|
} else if (check2.kind === "ulid") {
|
|
8352
8354
|
if (!ulidRegex.test(input.data)) {
|
|
@@ -8356,7 +8358,7 @@ var ZodString = class _ZodString2 extends ZodType {
|
|
|
8356
8358
|
code: ZodIssueCode.invalid_string,
|
|
8357
8359
|
message: check2.message
|
|
8358
8360
|
});
|
|
8359
|
-
|
|
8361
|
+
status2.dirty();
|
|
8360
8362
|
}
|
|
8361
8363
|
} else if (check2.kind === "url") {
|
|
8362
8364
|
try {
|
|
@@ -8368,7 +8370,7 @@ var ZodString = class _ZodString2 extends ZodType {
|
|
|
8368
8370
|
code: ZodIssueCode.invalid_string,
|
|
8369
8371
|
message: check2.message
|
|
8370
8372
|
});
|
|
8371
|
-
|
|
8373
|
+
status2.dirty();
|
|
8372
8374
|
}
|
|
8373
8375
|
} else if (check2.kind === "regex") {
|
|
8374
8376
|
check2.regex.lastIndex = 0;
|
|
@@ -8380,7 +8382,7 @@ var ZodString = class _ZodString2 extends ZodType {
|
|
|
8380
8382
|
code: ZodIssueCode.invalid_string,
|
|
8381
8383
|
message: check2.message
|
|
8382
8384
|
});
|
|
8383
|
-
|
|
8385
|
+
status2.dirty();
|
|
8384
8386
|
}
|
|
8385
8387
|
} else if (check2.kind === "trim") {
|
|
8386
8388
|
input.data = input.data.trim();
|
|
@@ -8392,7 +8394,7 @@ var ZodString = class _ZodString2 extends ZodType {
|
|
|
8392
8394
|
validation: { includes: check2.value, position: check2.position },
|
|
8393
8395
|
message: check2.message
|
|
8394
8396
|
});
|
|
8395
|
-
|
|
8397
|
+
status2.dirty();
|
|
8396
8398
|
}
|
|
8397
8399
|
} else if (check2.kind === "toLowerCase") {
|
|
8398
8400
|
input.data = input.data.toLowerCase();
|
|
@@ -8406,7 +8408,7 @@ var ZodString = class _ZodString2 extends ZodType {
|
|
|
8406
8408
|
validation: { startsWith: check2.value },
|
|
8407
8409
|
message: check2.message
|
|
8408
8410
|
});
|
|
8409
|
-
|
|
8411
|
+
status2.dirty();
|
|
8410
8412
|
}
|
|
8411
8413
|
} else if (check2.kind === "endsWith") {
|
|
8412
8414
|
if (!input.data.endsWith(check2.value)) {
|
|
@@ -8416,7 +8418,7 @@ var ZodString = class _ZodString2 extends ZodType {
|
|
|
8416
8418
|
validation: { endsWith: check2.value },
|
|
8417
8419
|
message: check2.message
|
|
8418
8420
|
});
|
|
8419
|
-
|
|
8421
|
+
status2.dirty();
|
|
8420
8422
|
}
|
|
8421
8423
|
} else if (check2.kind === "datetime") {
|
|
8422
8424
|
const regex = datetimeRegex(check2);
|
|
@@ -8427,7 +8429,7 @@ var ZodString = class _ZodString2 extends ZodType {
|
|
|
8427
8429
|
validation: "datetime",
|
|
8428
8430
|
message: check2.message
|
|
8429
8431
|
});
|
|
8430
|
-
|
|
8432
|
+
status2.dirty();
|
|
8431
8433
|
}
|
|
8432
8434
|
} else if (check2.kind === "date") {
|
|
8433
8435
|
const regex = dateRegex;
|
|
@@ -8438,7 +8440,7 @@ var ZodString = class _ZodString2 extends ZodType {
|
|
|
8438
8440
|
validation: "date",
|
|
8439
8441
|
message: check2.message
|
|
8440
8442
|
});
|
|
8441
|
-
|
|
8443
|
+
status2.dirty();
|
|
8442
8444
|
}
|
|
8443
8445
|
} else if (check2.kind === "time") {
|
|
8444
8446
|
const regex = timeRegex(check2);
|
|
@@ -8449,7 +8451,7 @@ var ZodString = class _ZodString2 extends ZodType {
|
|
|
8449
8451
|
validation: "time",
|
|
8450
8452
|
message: check2.message
|
|
8451
8453
|
});
|
|
8452
|
-
|
|
8454
|
+
status2.dirty();
|
|
8453
8455
|
}
|
|
8454
8456
|
} else if (check2.kind === "duration") {
|
|
8455
8457
|
if (!durationRegex.test(input.data)) {
|
|
@@ -8459,7 +8461,7 @@ var ZodString = class _ZodString2 extends ZodType {
|
|
|
8459
8461
|
code: ZodIssueCode.invalid_string,
|
|
8460
8462
|
message: check2.message
|
|
8461
8463
|
});
|
|
8462
|
-
|
|
8464
|
+
status2.dirty();
|
|
8463
8465
|
}
|
|
8464
8466
|
} else if (check2.kind === "ip") {
|
|
8465
8467
|
if (!isValidIP(input.data, check2.version)) {
|
|
@@ -8469,7 +8471,7 @@ var ZodString = class _ZodString2 extends ZodType {
|
|
|
8469
8471
|
code: ZodIssueCode.invalid_string,
|
|
8470
8472
|
message: check2.message
|
|
8471
8473
|
});
|
|
8472
|
-
|
|
8474
|
+
status2.dirty();
|
|
8473
8475
|
}
|
|
8474
8476
|
} else if (check2.kind === "jwt") {
|
|
8475
8477
|
if (!isValidJWT(input.data, check2.alg)) {
|
|
@@ -8479,7 +8481,7 @@ var ZodString = class _ZodString2 extends ZodType {
|
|
|
8479
8481
|
code: ZodIssueCode.invalid_string,
|
|
8480
8482
|
message: check2.message
|
|
8481
8483
|
});
|
|
8482
|
-
|
|
8484
|
+
status2.dirty();
|
|
8483
8485
|
}
|
|
8484
8486
|
} else if (check2.kind === "cidr") {
|
|
8485
8487
|
if (!isValidCidr(input.data, check2.version)) {
|
|
@@ -8489,7 +8491,7 @@ var ZodString = class _ZodString2 extends ZodType {
|
|
|
8489
8491
|
code: ZodIssueCode.invalid_string,
|
|
8490
8492
|
message: check2.message
|
|
8491
8493
|
});
|
|
8492
|
-
|
|
8494
|
+
status2.dirty();
|
|
8493
8495
|
}
|
|
8494
8496
|
} else if (check2.kind === "base64") {
|
|
8495
8497
|
if (!base64Regex.test(input.data)) {
|
|
@@ -8499,7 +8501,7 @@ var ZodString = class _ZodString2 extends ZodType {
|
|
|
8499
8501
|
code: ZodIssueCode.invalid_string,
|
|
8500
8502
|
message: check2.message
|
|
8501
8503
|
});
|
|
8502
|
-
|
|
8504
|
+
status2.dirty();
|
|
8503
8505
|
}
|
|
8504
8506
|
} else if (check2.kind === "base64url") {
|
|
8505
8507
|
if (!base64urlRegex.test(input.data)) {
|
|
@@ -8509,13 +8511,13 @@ var ZodString = class _ZodString2 extends ZodType {
|
|
|
8509
8511
|
code: ZodIssueCode.invalid_string,
|
|
8510
8512
|
message: check2.message
|
|
8511
8513
|
});
|
|
8512
|
-
|
|
8514
|
+
status2.dirty();
|
|
8513
8515
|
}
|
|
8514
8516
|
} else {
|
|
8515
8517
|
util.assertNever(check2);
|
|
8516
8518
|
}
|
|
8517
8519
|
}
|
|
8518
|
-
return { status:
|
|
8520
|
+
return { status: status2.value, value: input.data };
|
|
8519
8521
|
}
|
|
8520
8522
|
_regex(regex, validation, message) {
|
|
8521
8523
|
return this.refinement((data) => regex.test(data), {
|
|
@@ -8791,7 +8793,7 @@ var ZodNumber = class _ZodNumber extends ZodType {
|
|
|
8791
8793
|
return INVALID;
|
|
8792
8794
|
}
|
|
8793
8795
|
let ctx = void 0;
|
|
8794
|
-
const
|
|
8796
|
+
const status2 = new ParseStatus();
|
|
8795
8797
|
for (const check2 of this._def.checks) {
|
|
8796
8798
|
if (check2.kind === "int") {
|
|
8797
8799
|
if (!util.isInteger(input.data)) {
|
|
@@ -8802,7 +8804,7 @@ var ZodNumber = class _ZodNumber extends ZodType {
|
|
|
8802
8804
|
received: "float",
|
|
8803
8805
|
message: check2.message
|
|
8804
8806
|
});
|
|
8805
|
-
|
|
8807
|
+
status2.dirty();
|
|
8806
8808
|
}
|
|
8807
8809
|
} else if (check2.kind === "min") {
|
|
8808
8810
|
const tooSmall = check2.inclusive ? input.data < check2.value : input.data <= check2.value;
|
|
@@ -8816,7 +8818,7 @@ var ZodNumber = class _ZodNumber extends ZodType {
|
|
|
8816
8818
|
exact: false,
|
|
8817
8819
|
message: check2.message
|
|
8818
8820
|
});
|
|
8819
|
-
|
|
8821
|
+
status2.dirty();
|
|
8820
8822
|
}
|
|
8821
8823
|
} else if (check2.kind === "max") {
|
|
8822
8824
|
const tooBig = check2.inclusive ? input.data > check2.value : input.data >= check2.value;
|
|
@@ -8830,7 +8832,7 @@ var ZodNumber = class _ZodNumber extends ZodType {
|
|
|
8830
8832
|
exact: false,
|
|
8831
8833
|
message: check2.message
|
|
8832
8834
|
});
|
|
8833
|
-
|
|
8835
|
+
status2.dirty();
|
|
8834
8836
|
}
|
|
8835
8837
|
} else if (check2.kind === "multipleOf") {
|
|
8836
8838
|
if (floatSafeRemainder(input.data, check2.value) !== 0) {
|
|
@@ -8840,7 +8842,7 @@ var ZodNumber = class _ZodNumber extends ZodType {
|
|
|
8840
8842
|
multipleOf: check2.value,
|
|
8841
8843
|
message: check2.message
|
|
8842
8844
|
});
|
|
8843
|
-
|
|
8845
|
+
status2.dirty();
|
|
8844
8846
|
}
|
|
8845
8847
|
} else if (check2.kind === "finite") {
|
|
8846
8848
|
if (!Number.isFinite(input.data)) {
|
|
@@ -8849,13 +8851,13 @@ var ZodNumber = class _ZodNumber extends ZodType {
|
|
|
8849
8851
|
code: ZodIssueCode.not_finite,
|
|
8850
8852
|
message: check2.message
|
|
8851
8853
|
});
|
|
8852
|
-
|
|
8854
|
+
status2.dirty();
|
|
8853
8855
|
}
|
|
8854
8856
|
} else {
|
|
8855
8857
|
util.assertNever(check2);
|
|
8856
8858
|
}
|
|
8857
8859
|
}
|
|
8858
|
-
return { status:
|
|
8860
|
+
return { status: status2.value, value: input.data };
|
|
8859
8861
|
}
|
|
8860
8862
|
gte(value, message) {
|
|
8861
8863
|
return this.setLimit("min", value, true, errorUtil.toString(message));
|
|
@@ -9020,7 +9022,7 @@ var ZodBigInt = class _ZodBigInt extends ZodType {
|
|
|
9020
9022
|
return this._getInvalidInput(input);
|
|
9021
9023
|
}
|
|
9022
9024
|
let ctx = void 0;
|
|
9023
|
-
const
|
|
9025
|
+
const status2 = new ParseStatus();
|
|
9024
9026
|
for (const check2 of this._def.checks) {
|
|
9025
9027
|
if (check2.kind === "min") {
|
|
9026
9028
|
const tooSmall = check2.inclusive ? input.data < check2.value : input.data <= check2.value;
|
|
@@ -9033,7 +9035,7 @@ var ZodBigInt = class _ZodBigInt extends ZodType {
|
|
|
9033
9035
|
inclusive: check2.inclusive,
|
|
9034
9036
|
message: check2.message
|
|
9035
9037
|
});
|
|
9036
|
-
|
|
9038
|
+
status2.dirty();
|
|
9037
9039
|
}
|
|
9038
9040
|
} else if (check2.kind === "max") {
|
|
9039
9041
|
const tooBig = check2.inclusive ? input.data > check2.value : input.data >= check2.value;
|
|
@@ -9046,7 +9048,7 @@ var ZodBigInt = class _ZodBigInt extends ZodType {
|
|
|
9046
9048
|
inclusive: check2.inclusive,
|
|
9047
9049
|
message: check2.message
|
|
9048
9050
|
});
|
|
9049
|
-
|
|
9051
|
+
status2.dirty();
|
|
9050
9052
|
}
|
|
9051
9053
|
} else if (check2.kind === "multipleOf") {
|
|
9052
9054
|
if (input.data % check2.value !== BigInt(0)) {
|
|
@@ -9056,13 +9058,13 @@ var ZodBigInt = class _ZodBigInt extends ZodType {
|
|
|
9056
9058
|
multipleOf: check2.value,
|
|
9057
9059
|
message: check2.message
|
|
9058
9060
|
});
|
|
9059
|
-
|
|
9061
|
+
status2.dirty();
|
|
9060
9062
|
}
|
|
9061
9063
|
} else {
|
|
9062
9064
|
util.assertNever(check2);
|
|
9063
9065
|
}
|
|
9064
9066
|
}
|
|
9065
|
-
return { status:
|
|
9067
|
+
return { status: status2.value, value: input.data };
|
|
9066
9068
|
}
|
|
9067
9069
|
_getInvalidInput(input) {
|
|
9068
9070
|
const ctx = this._getOrReturnCtx(input);
|
|
@@ -9220,7 +9222,7 @@ var ZodDate = class _ZodDate extends ZodType {
|
|
|
9220
9222
|
});
|
|
9221
9223
|
return INVALID;
|
|
9222
9224
|
}
|
|
9223
|
-
const
|
|
9225
|
+
const status2 = new ParseStatus();
|
|
9224
9226
|
let ctx = void 0;
|
|
9225
9227
|
for (const check2 of this._def.checks) {
|
|
9226
9228
|
if (check2.kind === "min") {
|
|
@@ -9234,7 +9236,7 @@ var ZodDate = class _ZodDate extends ZodType {
|
|
|
9234
9236
|
minimum: check2.value,
|
|
9235
9237
|
type: "date"
|
|
9236
9238
|
});
|
|
9237
|
-
|
|
9239
|
+
status2.dirty();
|
|
9238
9240
|
}
|
|
9239
9241
|
} else if (check2.kind === "max") {
|
|
9240
9242
|
if (input.data.getTime() > check2.value) {
|
|
@@ -9247,14 +9249,14 @@ var ZodDate = class _ZodDate extends ZodType {
|
|
|
9247
9249
|
maximum: check2.value,
|
|
9248
9250
|
type: "date"
|
|
9249
9251
|
});
|
|
9250
|
-
|
|
9252
|
+
status2.dirty();
|
|
9251
9253
|
}
|
|
9252
9254
|
} else {
|
|
9253
9255
|
util.assertNever(check2);
|
|
9254
9256
|
}
|
|
9255
9257
|
}
|
|
9256
9258
|
return {
|
|
9257
|
-
status:
|
|
9259
|
+
status: status2.value,
|
|
9258
9260
|
value: new Date(input.data.getTime())
|
|
9259
9261
|
};
|
|
9260
9262
|
}
|
|
@@ -9440,7 +9442,7 @@ ZodVoid.create = (params) => {
|
|
|
9440
9442
|
};
|
|
9441
9443
|
var ZodArray = class _ZodArray extends ZodType {
|
|
9442
9444
|
_parse(input) {
|
|
9443
|
-
const { ctx, status } = this._processInputParams(input);
|
|
9445
|
+
const { ctx, status: status2 } = this._processInputParams(input);
|
|
9444
9446
|
const def = this._def;
|
|
9445
9447
|
if (ctx.parsedType !== ZodParsedType.array) {
|
|
9446
9448
|
addIssueToContext(ctx, {
|
|
@@ -9463,7 +9465,7 @@ var ZodArray = class _ZodArray extends ZodType {
|
|
|
9463
9465
|
exact: true,
|
|
9464
9466
|
message: def.exactLength.message
|
|
9465
9467
|
});
|
|
9466
|
-
|
|
9468
|
+
status2.dirty();
|
|
9467
9469
|
}
|
|
9468
9470
|
}
|
|
9469
9471
|
if (def.minLength !== null) {
|
|
@@ -9476,7 +9478,7 @@ var ZodArray = class _ZodArray extends ZodType {
|
|
|
9476
9478
|
exact: false,
|
|
9477
9479
|
message: def.minLength.message
|
|
9478
9480
|
});
|
|
9479
|
-
|
|
9481
|
+
status2.dirty();
|
|
9480
9482
|
}
|
|
9481
9483
|
}
|
|
9482
9484
|
if (def.maxLength !== null) {
|
|
@@ -9489,20 +9491,20 @@ var ZodArray = class _ZodArray extends ZodType {
|
|
|
9489
9491
|
exact: false,
|
|
9490
9492
|
message: def.maxLength.message
|
|
9491
9493
|
});
|
|
9492
|
-
|
|
9494
|
+
status2.dirty();
|
|
9493
9495
|
}
|
|
9494
9496
|
}
|
|
9495
9497
|
if (ctx.common.async) {
|
|
9496
9498
|
return Promise.all([...ctx.data].map((item, i) => {
|
|
9497
9499
|
return def.type._parseAsync(new ParseInputLazyPath(ctx, item, ctx.path, i));
|
|
9498
9500
|
})).then((result2) => {
|
|
9499
|
-
return ParseStatus.mergeArray(
|
|
9501
|
+
return ParseStatus.mergeArray(status2, result2);
|
|
9500
9502
|
});
|
|
9501
9503
|
}
|
|
9502
9504
|
const result = [...ctx.data].map((item, i) => {
|
|
9503
9505
|
return def.type._parseSync(new ParseInputLazyPath(ctx, item, ctx.path, i));
|
|
9504
9506
|
});
|
|
9505
|
-
return ParseStatus.mergeArray(
|
|
9507
|
+
return ParseStatus.mergeArray(status2, result);
|
|
9506
9508
|
}
|
|
9507
9509
|
get element() {
|
|
9508
9510
|
return this._def.type;
|
|
@@ -9591,7 +9593,7 @@ var ZodObject = class _ZodObject extends ZodType {
|
|
|
9591
9593
|
});
|
|
9592
9594
|
return INVALID;
|
|
9593
9595
|
}
|
|
9594
|
-
const { status, ctx } = this._processInputParams(input);
|
|
9596
|
+
const { status: status2, ctx } = this._processInputParams(input);
|
|
9595
9597
|
const { shape, keys: shapeKeys } = this._getCached();
|
|
9596
9598
|
const extraKeys = [];
|
|
9597
9599
|
if (!(this._def.catchall instanceof ZodNever && this._def.unknownKeys === "strip")) {
|
|
@@ -9626,7 +9628,7 @@ var ZodObject = class _ZodObject extends ZodType {
|
|
|
9626
9628
|
code: ZodIssueCode.unrecognized_keys,
|
|
9627
9629
|
keys: extraKeys
|
|
9628
9630
|
});
|
|
9629
|
-
|
|
9631
|
+
status2.dirty();
|
|
9630
9632
|
}
|
|
9631
9633
|
} else if (unknownKeys === "strip") {
|
|
9632
9634
|
} else {
|
|
@@ -9660,10 +9662,10 @@ var ZodObject = class _ZodObject extends ZodType {
|
|
|
9660
9662
|
}
|
|
9661
9663
|
return syncPairs;
|
|
9662
9664
|
}).then((syncPairs) => {
|
|
9663
|
-
return ParseStatus.mergeObjectSync(
|
|
9665
|
+
return ParseStatus.mergeObjectSync(status2, syncPairs);
|
|
9664
9666
|
});
|
|
9665
9667
|
} else {
|
|
9666
|
-
return ParseStatus.mergeObjectSync(
|
|
9668
|
+
return ParseStatus.mergeObjectSync(status2, pairs);
|
|
9667
9669
|
}
|
|
9668
9670
|
}
|
|
9669
9671
|
get shape() {
|
|
@@ -10141,7 +10143,7 @@ function mergeValues(a, b) {
|
|
|
10141
10143
|
}
|
|
10142
10144
|
var ZodIntersection = class extends ZodType {
|
|
10143
10145
|
_parse(input) {
|
|
10144
|
-
const { status, ctx } = this._processInputParams(input);
|
|
10146
|
+
const { status: status2, ctx } = this._processInputParams(input);
|
|
10145
10147
|
const handleParsed = (parsedLeft, parsedRight) => {
|
|
10146
10148
|
if (isAborted(parsedLeft) || isAborted(parsedRight)) {
|
|
10147
10149
|
return INVALID;
|
|
@@ -10154,9 +10156,9 @@ var ZodIntersection = class extends ZodType {
|
|
|
10154
10156
|
return INVALID;
|
|
10155
10157
|
}
|
|
10156
10158
|
if (isDirty(parsedLeft) || isDirty(parsedRight)) {
|
|
10157
|
-
|
|
10159
|
+
status2.dirty();
|
|
10158
10160
|
}
|
|
10159
|
-
return { status:
|
|
10161
|
+
return { status: status2.value, value: merged.data };
|
|
10160
10162
|
};
|
|
10161
10163
|
if (ctx.common.async) {
|
|
10162
10164
|
return Promise.all([
|
|
@@ -10194,7 +10196,7 @@ ZodIntersection.create = (left, right, params) => {
|
|
|
10194
10196
|
};
|
|
10195
10197
|
var ZodTuple = class _ZodTuple extends ZodType {
|
|
10196
10198
|
_parse(input) {
|
|
10197
|
-
const { status, ctx } = this._processInputParams(input);
|
|
10199
|
+
const { status: status2, ctx } = this._processInputParams(input);
|
|
10198
10200
|
if (ctx.parsedType !== ZodParsedType.array) {
|
|
10199
10201
|
addIssueToContext(ctx, {
|
|
10200
10202
|
code: ZodIssueCode.invalid_type,
|
|
@@ -10222,7 +10224,7 @@ var ZodTuple = class _ZodTuple extends ZodType {
|
|
|
10222
10224
|
exact: false,
|
|
10223
10225
|
type: "array"
|
|
10224
10226
|
});
|
|
10225
|
-
|
|
10227
|
+
status2.dirty();
|
|
10226
10228
|
}
|
|
10227
10229
|
const items = [...ctx.data].map((item, itemIndex) => {
|
|
10228
10230
|
const schema = this._def.items[itemIndex] || this._def.rest;
|
|
@@ -10232,10 +10234,10 @@ var ZodTuple = class _ZodTuple extends ZodType {
|
|
|
10232
10234
|
}).filter((x) => !!x);
|
|
10233
10235
|
if (ctx.common.async) {
|
|
10234
10236
|
return Promise.all(items).then((results) => {
|
|
10235
|
-
return ParseStatus.mergeArray(
|
|
10237
|
+
return ParseStatus.mergeArray(status2, results);
|
|
10236
10238
|
});
|
|
10237
10239
|
} else {
|
|
10238
|
-
return ParseStatus.mergeArray(
|
|
10240
|
+
return ParseStatus.mergeArray(status2, items);
|
|
10239
10241
|
}
|
|
10240
10242
|
}
|
|
10241
10243
|
get items() {
|
|
@@ -10267,7 +10269,7 @@ var ZodRecord = class _ZodRecord extends ZodType {
|
|
|
10267
10269
|
return this._def.valueType;
|
|
10268
10270
|
}
|
|
10269
10271
|
_parse(input) {
|
|
10270
|
-
const { status, ctx } = this._processInputParams(input);
|
|
10272
|
+
const { status: status2, ctx } = this._processInputParams(input);
|
|
10271
10273
|
if (ctx.parsedType !== ZodParsedType.object) {
|
|
10272
10274
|
addIssueToContext(ctx, {
|
|
10273
10275
|
code: ZodIssueCode.invalid_type,
|
|
@@ -10287,9 +10289,9 @@ var ZodRecord = class _ZodRecord extends ZodType {
|
|
|
10287
10289
|
});
|
|
10288
10290
|
}
|
|
10289
10291
|
if (ctx.common.async) {
|
|
10290
|
-
return ParseStatus.mergeObjectAsync(
|
|
10292
|
+
return ParseStatus.mergeObjectAsync(status2, pairs);
|
|
10291
10293
|
} else {
|
|
10292
|
-
return ParseStatus.mergeObjectSync(
|
|
10294
|
+
return ParseStatus.mergeObjectSync(status2, pairs);
|
|
10293
10295
|
}
|
|
10294
10296
|
}
|
|
10295
10297
|
get element() {
|
|
@@ -10320,7 +10322,7 @@ var ZodMap = class extends ZodType {
|
|
|
10320
10322
|
return this._def.valueType;
|
|
10321
10323
|
}
|
|
10322
10324
|
_parse(input) {
|
|
10323
|
-
const { status, ctx } = this._processInputParams(input);
|
|
10325
|
+
const { status: status2, ctx } = this._processInputParams(input);
|
|
10324
10326
|
if (ctx.parsedType !== ZodParsedType.map) {
|
|
10325
10327
|
addIssueToContext(ctx, {
|
|
10326
10328
|
code: ZodIssueCode.invalid_type,
|
|
@@ -10347,11 +10349,11 @@ var ZodMap = class extends ZodType {
|
|
|
10347
10349
|
return INVALID;
|
|
10348
10350
|
}
|
|
10349
10351
|
if (key.status === "dirty" || value.status === "dirty") {
|
|
10350
|
-
|
|
10352
|
+
status2.dirty();
|
|
10351
10353
|
}
|
|
10352
10354
|
finalMap.set(key.value, value.value);
|
|
10353
10355
|
}
|
|
10354
|
-
return { status:
|
|
10356
|
+
return { status: status2.value, value: finalMap };
|
|
10355
10357
|
});
|
|
10356
10358
|
} else {
|
|
10357
10359
|
const finalMap = /* @__PURE__ */ new Map();
|
|
@@ -10362,11 +10364,11 @@ var ZodMap = class extends ZodType {
|
|
|
10362
10364
|
return INVALID;
|
|
10363
10365
|
}
|
|
10364
10366
|
if (key.status === "dirty" || value.status === "dirty") {
|
|
10365
|
-
|
|
10367
|
+
status2.dirty();
|
|
10366
10368
|
}
|
|
10367
10369
|
finalMap.set(key.value, value.value);
|
|
10368
10370
|
}
|
|
10369
|
-
return { status:
|
|
10371
|
+
return { status: status2.value, value: finalMap };
|
|
10370
10372
|
}
|
|
10371
10373
|
}
|
|
10372
10374
|
};
|
|
@@ -10380,7 +10382,7 @@ ZodMap.create = (keyType, valueType, params) => {
|
|
|
10380
10382
|
};
|
|
10381
10383
|
var ZodSet = class _ZodSet extends ZodType {
|
|
10382
10384
|
_parse(input) {
|
|
10383
|
-
const { status, ctx } = this._processInputParams(input);
|
|
10385
|
+
const { status: status2, ctx } = this._processInputParams(input);
|
|
10384
10386
|
if (ctx.parsedType !== ZodParsedType.set) {
|
|
10385
10387
|
addIssueToContext(ctx, {
|
|
10386
10388
|
code: ZodIssueCode.invalid_type,
|
|
@@ -10400,7 +10402,7 @@ var ZodSet = class _ZodSet extends ZodType {
|
|
|
10400
10402
|
exact: false,
|
|
10401
10403
|
message: def.minSize.message
|
|
10402
10404
|
});
|
|
10403
|
-
|
|
10405
|
+
status2.dirty();
|
|
10404
10406
|
}
|
|
10405
10407
|
}
|
|
10406
10408
|
if (def.maxSize !== null) {
|
|
@@ -10413,7 +10415,7 @@ var ZodSet = class _ZodSet extends ZodType {
|
|
|
10413
10415
|
exact: false,
|
|
10414
10416
|
message: def.maxSize.message
|
|
10415
10417
|
});
|
|
10416
|
-
|
|
10418
|
+
status2.dirty();
|
|
10417
10419
|
}
|
|
10418
10420
|
}
|
|
10419
10421
|
const valueType = this._def.valueType;
|
|
@@ -10423,10 +10425,10 @@ var ZodSet = class _ZodSet extends ZodType {
|
|
|
10423
10425
|
if (element.status === "aborted")
|
|
10424
10426
|
return INVALID;
|
|
10425
10427
|
if (element.status === "dirty")
|
|
10426
|
-
|
|
10428
|
+
status2.dirty();
|
|
10427
10429
|
parsedSet.add(element.value);
|
|
10428
10430
|
}
|
|
10429
|
-
return { status:
|
|
10431
|
+
return { status: status2.value, value: parsedSet };
|
|
10430
10432
|
}
|
|
10431
10433
|
const elements = [...ctx.data.values()].map((item, i) => valueType._parse(new ParseInputLazyPath(ctx, item, ctx.path, i)));
|
|
10432
10434
|
if (ctx.common.async) {
|
|
@@ -10757,15 +10759,15 @@ var ZodEffects = class extends ZodType {
|
|
|
10757
10759
|
return this._def.schema._def.typeName === ZodFirstPartyTypeKind.ZodEffects ? this._def.schema.sourceType() : this._def.schema;
|
|
10758
10760
|
}
|
|
10759
10761
|
_parse(input) {
|
|
10760
|
-
const { status, ctx } = this._processInputParams(input);
|
|
10762
|
+
const { status: status2, ctx } = this._processInputParams(input);
|
|
10761
10763
|
const effect = this._def.effect || null;
|
|
10762
10764
|
const checkCtx = {
|
|
10763
10765
|
addIssue: (arg) => {
|
|
10764
10766
|
addIssueToContext(ctx, arg);
|
|
10765
10767
|
if (arg.fatal) {
|
|
10766
|
-
|
|
10768
|
+
status2.abort();
|
|
10767
10769
|
} else {
|
|
10768
|
-
|
|
10770
|
+
status2.dirty();
|
|
10769
10771
|
}
|
|
10770
10772
|
},
|
|
10771
10773
|
get path() {
|
|
@@ -10777,7 +10779,7 @@ var ZodEffects = class extends ZodType {
|
|
|
10777
10779
|
const processed = effect.transform(ctx.data, checkCtx);
|
|
10778
10780
|
if (ctx.common.async) {
|
|
10779
10781
|
return Promise.resolve(processed).then(async (processed2) => {
|
|
10780
|
-
if (
|
|
10782
|
+
if (status2.value === "aborted")
|
|
10781
10783
|
return INVALID;
|
|
10782
10784
|
const result = await this._def.schema._parseAsync({
|
|
10783
10785
|
data: processed2,
|
|
@@ -10788,12 +10790,12 @@ var ZodEffects = class extends ZodType {
|
|
|
10788
10790
|
return INVALID;
|
|
10789
10791
|
if (result.status === "dirty")
|
|
10790
10792
|
return DIRTY(result.value);
|
|
10791
|
-
if (
|
|
10793
|
+
if (status2.value === "dirty")
|
|
10792
10794
|
return DIRTY(result.value);
|
|
10793
10795
|
return result;
|
|
10794
10796
|
});
|
|
10795
10797
|
} else {
|
|
10796
|
-
if (
|
|
10798
|
+
if (status2.value === "aborted")
|
|
10797
10799
|
return INVALID;
|
|
10798
10800
|
const result = this._def.schema._parseSync({
|
|
10799
10801
|
data: processed,
|
|
@@ -10804,7 +10806,7 @@ var ZodEffects = class extends ZodType {
|
|
|
10804
10806
|
return INVALID;
|
|
10805
10807
|
if (result.status === "dirty")
|
|
10806
10808
|
return DIRTY(result.value);
|
|
10807
|
-
if (
|
|
10809
|
+
if (status2.value === "dirty")
|
|
10808
10810
|
return DIRTY(result.value);
|
|
10809
10811
|
return result;
|
|
10810
10812
|
}
|
|
@@ -10829,17 +10831,17 @@ var ZodEffects = class extends ZodType {
|
|
|
10829
10831
|
if (inner.status === "aborted")
|
|
10830
10832
|
return INVALID;
|
|
10831
10833
|
if (inner.status === "dirty")
|
|
10832
|
-
|
|
10834
|
+
status2.dirty();
|
|
10833
10835
|
executeRefinement(inner.value);
|
|
10834
|
-
return { status:
|
|
10836
|
+
return { status: status2.value, value: inner.value };
|
|
10835
10837
|
} else {
|
|
10836
10838
|
return this._def.schema._parseAsync({ data: ctx.data, path: ctx.path, parent: ctx }).then((inner) => {
|
|
10837
10839
|
if (inner.status === "aborted")
|
|
10838
10840
|
return INVALID;
|
|
10839
10841
|
if (inner.status === "dirty")
|
|
10840
|
-
|
|
10842
|
+
status2.dirty();
|
|
10841
10843
|
return executeRefinement(inner.value).then(() => {
|
|
10842
|
-
return { status:
|
|
10844
|
+
return { status: status2.value, value: inner.value };
|
|
10843
10845
|
});
|
|
10844
10846
|
});
|
|
10845
10847
|
}
|
|
@@ -10857,13 +10859,13 @@ var ZodEffects = class extends ZodType {
|
|
|
10857
10859
|
if (result instanceof Promise) {
|
|
10858
10860
|
throw new Error(`Asynchronous transform encountered during synchronous parse operation. Use .parseAsync instead.`);
|
|
10859
10861
|
}
|
|
10860
|
-
return { status:
|
|
10862
|
+
return { status: status2.value, value: result };
|
|
10861
10863
|
} else {
|
|
10862
10864
|
return this._def.schema._parseAsync({ data: ctx.data, path: ctx.path, parent: ctx }).then((base) => {
|
|
10863
10865
|
if (!isValid(base))
|
|
10864
10866
|
return INVALID;
|
|
10865
10867
|
return Promise.resolve(effect.transform(base.value, checkCtx)).then((result) => ({
|
|
10866
|
-
status:
|
|
10868
|
+
status: status2.value,
|
|
10867
10869
|
value: result
|
|
10868
10870
|
}));
|
|
10869
10871
|
});
|
|
@@ -11042,7 +11044,7 @@ var ZodBranded = class extends ZodType {
|
|
|
11042
11044
|
};
|
|
11043
11045
|
var ZodPipeline = class _ZodPipeline extends ZodType {
|
|
11044
11046
|
_parse(input) {
|
|
11045
|
-
const { status, ctx } = this._processInputParams(input);
|
|
11047
|
+
const { status: status2, ctx } = this._processInputParams(input);
|
|
11046
11048
|
if (ctx.common.async) {
|
|
11047
11049
|
const handleAsync = async () => {
|
|
11048
11050
|
const inResult = await this._def.in._parseAsync({
|
|
@@ -11053,7 +11055,7 @@ var ZodPipeline = class _ZodPipeline extends ZodType {
|
|
|
11053
11055
|
if (inResult.status === "aborted")
|
|
11054
11056
|
return INVALID;
|
|
11055
11057
|
if (inResult.status === "dirty") {
|
|
11056
|
-
|
|
11058
|
+
status2.dirty();
|
|
11057
11059
|
return DIRTY(inResult.value);
|
|
11058
11060
|
} else {
|
|
11059
11061
|
return this._def.out._parseAsync({
|
|
@@ -11073,7 +11075,7 @@ var ZodPipeline = class _ZodPipeline extends ZodType {
|
|
|
11073
11075
|
if (inResult.status === "aborted")
|
|
11074
11076
|
return INVALID;
|
|
11075
11077
|
if (inResult.status === "dirty") {
|
|
11076
|
-
|
|
11078
|
+
status2.dirty();
|
|
11077
11079
|
return {
|
|
11078
11080
|
status: "dirty",
|
|
11079
11081
|
value: inResult.value
|
|
@@ -11433,10 +11435,10 @@ function assignProp(target, prop, value) {
|
|
|
11433
11435
|
configurable: true
|
|
11434
11436
|
});
|
|
11435
11437
|
}
|
|
11436
|
-
function getElementAtPath(obj,
|
|
11437
|
-
if (!
|
|
11438
|
+
function getElementAtPath(obj, path6) {
|
|
11439
|
+
if (!path6)
|
|
11438
11440
|
return obj;
|
|
11439
|
-
return
|
|
11441
|
+
return path6.reduce((acc, key) => acc?.[key], obj);
|
|
11440
11442
|
}
|
|
11441
11443
|
function promiseAllObject(promisesObj) {
|
|
11442
11444
|
const keys = Object.keys(promisesObj);
|
|
@@ -11756,11 +11758,11 @@ function aborted(x, startIndex = 0) {
|
|
|
11756
11758
|
}
|
|
11757
11759
|
return false;
|
|
11758
11760
|
}
|
|
11759
|
-
function prefixIssues(
|
|
11761
|
+
function prefixIssues(path6, issues) {
|
|
11760
11762
|
return issues.map((iss) => {
|
|
11761
11763
|
var _a;
|
|
11762
11764
|
(_a = iss).path ?? (_a.path = []);
|
|
11763
|
-
iss.path.unshift(
|
|
11765
|
+
iss.path.unshift(path6);
|
|
11764
11766
|
return iss;
|
|
11765
11767
|
});
|
|
11766
11768
|
}
|
|
@@ -15171,11 +15173,11 @@ function normalizeObjectSchema(schema) {
|
|
|
15171
15173
|
}
|
|
15172
15174
|
return void 0;
|
|
15173
15175
|
}
|
|
15174
|
-
function getDotPath(
|
|
15175
|
-
if (
|
|
15176
|
+
function getDotPath(path6) {
|
|
15177
|
+
if (path6.length === 0) {
|
|
15176
15178
|
return "object root";
|
|
15177
15179
|
}
|
|
15178
|
-
return
|
|
15180
|
+
return path6.reduce((acc, seg, index) => {
|
|
15179
15181
|
if (index === 0) {
|
|
15180
15182
|
return String(seg);
|
|
15181
15183
|
}
|
|
@@ -17474,8 +17476,8 @@ var UrlElicitationRequiredError = class extends McpError {
|
|
|
17474
17476
|
};
|
|
17475
17477
|
|
|
17476
17478
|
// ../../node_modules/@modelcontextprotocol/sdk/dist/esm/experimental/tasks/interfaces.js
|
|
17477
|
-
function isTerminal(
|
|
17478
|
-
return
|
|
17479
|
+
function isTerminal(status2) {
|
|
17480
|
+
return status2 === "completed" || status2 === "failed" || status2 === "cancelled";
|
|
17479
17481
|
}
|
|
17480
17482
|
|
|
17481
17483
|
// ../../node_modules/zod-to-json-schema/dist/esm/Options.js
|
|
@@ -17534,19 +17536,19 @@ var getRefs = (options) => {
|
|
|
17534
17536
|
};
|
|
17535
17537
|
|
|
17536
17538
|
// ../../node_modules/zod-to-json-schema/dist/esm/errorMessages.js
|
|
17537
|
-
function addErrorMessage(res, key,
|
|
17539
|
+
function addErrorMessage(res, key, errorMessage2, refs) {
|
|
17538
17540
|
if (!refs?.errorMessages)
|
|
17539
17541
|
return;
|
|
17540
|
-
if (
|
|
17542
|
+
if (errorMessage2) {
|
|
17541
17543
|
res.errorMessage = {
|
|
17542
17544
|
...res.errorMessage,
|
|
17543
|
-
[key]:
|
|
17545
|
+
[key]: errorMessage2
|
|
17544
17546
|
};
|
|
17545
17547
|
}
|
|
17546
17548
|
}
|
|
17547
|
-
function setResponseValueAndErrors(res, key, value,
|
|
17549
|
+
function setResponseValueAndErrors(res, key, value, errorMessage2, refs) {
|
|
17548
17550
|
res[key] = value;
|
|
17549
|
-
addErrorMessage(res, key,
|
|
17551
|
+
addErrorMessage(res, key, errorMessage2, refs);
|
|
17550
17552
|
}
|
|
17551
17553
|
|
|
17552
17554
|
// ../../node_modules/zod-to-json-schema/dist/esm/getRelativePath.js
|
|
@@ -18857,8 +18859,8 @@ var Protocol = class {
|
|
|
18857
18859
|
if (queuedMessage.type === "response") {
|
|
18858
18860
|
resolver(message);
|
|
18859
18861
|
} else {
|
|
18860
|
-
const
|
|
18861
|
-
const error2 = new McpError(
|
|
18862
|
+
const errorMessage2 = message;
|
|
18863
|
+
const error2 = new McpError(errorMessage2.error.code, errorMessage2.error.message, errorMessage2.error.data);
|
|
18862
18864
|
resolver(error2);
|
|
18863
18865
|
}
|
|
18864
18866
|
} else {
|
|
@@ -19694,8 +19696,8 @@ var Protocol = class {
|
|
|
19694
19696
|
}
|
|
19695
19697
|
return task;
|
|
19696
19698
|
},
|
|
19697
|
-
storeTaskResult: async (taskId,
|
|
19698
|
-
await taskStore.storeTaskResult(taskId,
|
|
19699
|
+
storeTaskResult: async (taskId, status2, result) => {
|
|
19700
|
+
await taskStore.storeTaskResult(taskId, status2, result, sessionId);
|
|
19699
19701
|
const task = await taskStore.getTask(taskId, sessionId);
|
|
19700
19702
|
if (task) {
|
|
19701
19703
|
const notification = TaskStatusNotificationSchema.parse({
|
|
@@ -19711,15 +19713,15 @@ var Protocol = class {
|
|
|
19711
19713
|
getTaskResult: (taskId) => {
|
|
19712
19714
|
return taskStore.getTaskResult(taskId, sessionId);
|
|
19713
19715
|
},
|
|
19714
|
-
updateTaskStatus: async (taskId,
|
|
19716
|
+
updateTaskStatus: async (taskId, status2, statusMessage) => {
|
|
19715
19717
|
const task = await taskStore.getTask(taskId, sessionId);
|
|
19716
19718
|
if (!task) {
|
|
19717
19719
|
throw new McpError(ErrorCode.InvalidParams, `Task "${taskId}" not found - it may have been cleaned up`);
|
|
19718
19720
|
}
|
|
19719
19721
|
if (isTerminal(task.status)) {
|
|
19720
|
-
throw new McpError(ErrorCode.InvalidParams, `Cannot update task "${taskId}" from terminal status "${task.status}" to "${
|
|
19722
|
+
throw new McpError(ErrorCode.InvalidParams, `Cannot update task "${taskId}" from terminal status "${task.status}" to "${status2}". Terminal states (completed, failed, cancelled) cannot transition to other states.`);
|
|
19721
19723
|
}
|
|
19722
|
-
await taskStore.updateTaskStatus(taskId,
|
|
19724
|
+
await taskStore.updateTaskStatus(taskId, status2, statusMessage, sessionId);
|
|
19723
19725
|
const updatedTask = await taskStore.getTask(taskId, sessionId);
|
|
19724
19726
|
if (updatedTask) {
|
|
19725
19727
|
const notification = TaskStatusNotificationSchema.parse({
|
|
@@ -20149,23 +20151,23 @@ var Server = class extends Protocol {
|
|
|
20149
20151
|
const wrappedHandler = async (request, extra) => {
|
|
20150
20152
|
const validatedRequest = safeParse2(CallToolRequestSchema, request);
|
|
20151
20153
|
if (!validatedRequest.success) {
|
|
20152
|
-
const
|
|
20153
|
-
throw new McpError(ErrorCode.InvalidParams, `Invalid tools/call request: ${
|
|
20154
|
+
const errorMessage2 = validatedRequest.error instanceof Error ? validatedRequest.error.message : String(validatedRequest.error);
|
|
20155
|
+
throw new McpError(ErrorCode.InvalidParams, `Invalid tools/call request: ${errorMessage2}`);
|
|
20154
20156
|
}
|
|
20155
20157
|
const { params } = validatedRequest.data;
|
|
20156
20158
|
const result = await Promise.resolve(handler(request, extra));
|
|
20157
20159
|
if (params.task) {
|
|
20158
20160
|
const taskValidationResult = safeParse2(CreateTaskResultSchema, result);
|
|
20159
20161
|
if (!taskValidationResult.success) {
|
|
20160
|
-
const
|
|
20161
|
-
throw new McpError(ErrorCode.InvalidParams, `Invalid task creation result: ${
|
|
20162
|
+
const errorMessage2 = taskValidationResult.error instanceof Error ? taskValidationResult.error.message : String(taskValidationResult.error);
|
|
20163
|
+
throw new McpError(ErrorCode.InvalidParams, `Invalid task creation result: ${errorMessage2}`);
|
|
20162
20164
|
}
|
|
20163
20165
|
return taskValidationResult.data;
|
|
20164
20166
|
}
|
|
20165
20167
|
const validationResult = safeParse2(CallToolResultSchema, result);
|
|
20166
20168
|
if (!validationResult.success) {
|
|
20167
|
-
const
|
|
20168
|
-
throw new McpError(ErrorCode.InvalidParams, `Invalid tools/call result: ${
|
|
20169
|
+
const errorMessage2 = validationResult.error instanceof Error ? validationResult.error.message : String(validationResult.error);
|
|
20170
|
+
throw new McpError(ErrorCode.InvalidParams, `Invalid tools/call result: ${errorMessage2}`);
|
|
20169
20171
|
}
|
|
20170
20172
|
return validationResult.data;
|
|
20171
20173
|
};
|
|
@@ -20659,12 +20661,12 @@ var McpServer = class {
|
|
|
20659
20661
|
* @param errorMessage - The error message.
|
|
20660
20662
|
* @returns The tool error result.
|
|
20661
20663
|
*/
|
|
20662
|
-
createToolError(
|
|
20664
|
+
createToolError(errorMessage2) {
|
|
20663
20665
|
return {
|
|
20664
20666
|
content: [
|
|
20665
20667
|
{
|
|
20666
20668
|
type: "text",
|
|
20667
|
-
text:
|
|
20669
|
+
text: errorMessage2
|
|
20668
20670
|
}
|
|
20669
20671
|
],
|
|
20670
20672
|
isError: true
|
|
@@ -20682,8 +20684,8 @@ var McpServer = class {
|
|
|
20682
20684
|
const parseResult = await safeParseAsync2(schemaToParse, args);
|
|
20683
20685
|
if (!parseResult.success) {
|
|
20684
20686
|
const error2 = "error" in parseResult ? parseResult.error : "Unknown error";
|
|
20685
|
-
const
|
|
20686
|
-
throw new McpError(ErrorCode.InvalidParams, `Input validation error: Invalid arguments for tool ${toolName}: ${
|
|
20687
|
+
const errorMessage2 = getParseErrorMessage(error2);
|
|
20688
|
+
throw new McpError(ErrorCode.InvalidParams, `Input validation error: Invalid arguments for tool ${toolName}: ${errorMessage2}`);
|
|
20687
20689
|
}
|
|
20688
20690
|
return parseResult.data;
|
|
20689
20691
|
}
|
|
@@ -20707,8 +20709,8 @@ var McpServer = class {
|
|
|
20707
20709
|
const parseResult = await safeParseAsync2(outputObj, result.structuredContent);
|
|
20708
20710
|
if (!parseResult.success) {
|
|
20709
20711
|
const error2 = "error" in parseResult ? parseResult.error : "Unknown error";
|
|
20710
|
-
const
|
|
20711
|
-
throw new McpError(ErrorCode.InvalidParams, `Output validation error: Invalid structured content for tool ${toolName}: ${
|
|
20712
|
+
const errorMessage2 = getParseErrorMessage(error2);
|
|
20713
|
+
throw new McpError(ErrorCode.InvalidParams, `Output validation error: Invalid structured content for tool ${toolName}: ${errorMessage2}`);
|
|
20712
20714
|
}
|
|
20713
20715
|
}
|
|
20714
20716
|
/**
|
|
@@ -20920,8 +20922,8 @@ var McpServer = class {
|
|
|
20920
20922
|
const parseResult = await safeParseAsync2(argsObj, request.params.arguments);
|
|
20921
20923
|
if (!parseResult.success) {
|
|
20922
20924
|
const error2 = "error" in parseResult ? parseResult.error : "Unknown error";
|
|
20923
|
-
const
|
|
20924
|
-
throw new McpError(ErrorCode.InvalidParams, `Invalid arguments for prompt ${request.params.name}: ${
|
|
20925
|
+
const errorMessage2 = getParseErrorMessage(error2);
|
|
20926
|
+
throw new McpError(ErrorCode.InvalidParams, `Invalid arguments for prompt ${request.params.name}: ${errorMessage2}`);
|
|
20925
20927
|
}
|
|
20926
20928
|
const args = parseResult.data;
|
|
20927
20929
|
const cb = prompt.callback;
|
|
@@ -21431,32 +21433,47 @@ var StdioServerTransport = class {
|
|
|
21431
21433
|
}
|
|
21432
21434
|
};
|
|
21433
21435
|
|
|
21436
|
+
// src/native.ts
|
|
21437
|
+
import { createRequire } from "node:module";
|
|
21438
|
+
import native from "../native/index.js";
|
|
21439
|
+
var { createScope, getScope, forkScope, deleteScope, warmModelRequest, doctor, machine, health, status, usage, createSession, deleteSession, killSession, listModels, route, runTaskRequest, runTaskBatchRequest, listTaskJobs, getTaskJob, cancelTaskJob, removeTaskJob } = native;
|
|
21440
|
+
var { version: SERVER_VERSION } = createRequire(import.meta.url)("../package.json");
|
|
21441
|
+
|
|
21442
|
+
// src/lifecycle.ts
|
|
21443
|
+
import { spawn as spawn2 } from "node:child_process";
|
|
21444
|
+
|
|
21445
|
+
// src/serve.ts
|
|
21446
|
+
import { spawn } from "node:child_process";
|
|
21447
|
+
import { existsSync as existsSync2 } from "node:fs";
|
|
21448
|
+
import { createRequire as createRequire2 } from "node:module";
|
|
21449
|
+
import path2 from "node:path";
|
|
21450
|
+
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
21451
|
+
|
|
21434
21452
|
// src/config.ts
|
|
21435
21453
|
import { existsSync } from "node:fs";
|
|
21436
21454
|
import { realpath } from "node:fs/promises";
|
|
21437
21455
|
import path from "node:path";
|
|
21438
21456
|
import { fileURLToPath } from "node:url";
|
|
21439
21457
|
import { homedir } from "node:os";
|
|
21458
|
+
var PACKAGE_ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
|
|
21440
21459
|
function findRepoRoot() {
|
|
21441
21460
|
let dir = path.dirname(fileURLToPath(import.meta.url));
|
|
21442
21461
|
for (let depth = 0; depth < 10; depth += 1) {
|
|
21443
|
-
if (existsSync(path.join(dir, "Cargo.toml"))) return dir;
|
|
21462
|
+
if (existsSync(path.join(dir, "Cargo.toml")) && path.join(dir, "packages", "mcp") === PACKAGE_ROOT) return dir;
|
|
21444
21463
|
const parent = path.dirname(dir);
|
|
21445
21464
|
if (parent === dir) break;
|
|
21446
21465
|
dir = parent;
|
|
21447
21466
|
}
|
|
21448
21467
|
return void 0;
|
|
21449
21468
|
}
|
|
21450
|
-
var
|
|
21451
|
-
var
|
|
21469
|
+
var CHECKOUT_ROOT = findRepoRoot();
|
|
21470
|
+
var REPO_ROOT = CHECKOUT_ROOT ?? PACKAGE_ROOT;
|
|
21452
21471
|
function adapterPath(file) {
|
|
21453
|
-
|
|
21454
|
-
if (existsSync(bundled)) return bundled;
|
|
21455
|
-
return path.join(REPO_ROOT, "benchmark/local/scripts", file);
|
|
21472
|
+
return path.join(PACKAGE_ROOT, "dist", "research", file);
|
|
21456
21473
|
}
|
|
21457
21474
|
var RESEARCH_ADAPTERS = {
|
|
21458
|
-
bash: adapterPath("
|
|
21459
|
-
octocode: adapterPath("
|
|
21475
|
+
bash: adapterPath("bash-agent.js"),
|
|
21476
|
+
octocode: adapterPath("octocode-agent.js")
|
|
21460
21477
|
};
|
|
21461
21478
|
var DEFAULT_RESEARCH_ADAPTER = process.env.FREELLAMA_MCP_DEFAULT_ADAPTER === "octocode" ? "octocode" : "bash";
|
|
21462
21479
|
function envInt(name, fallback) {
|
|
@@ -21467,7 +21484,7 @@ function envInt(name, fallback) {
|
|
|
21467
21484
|
}
|
|
21468
21485
|
var DEFAULT_OLLAMA_ENDPOINT = process.env.FREELLAMA_OLLAMA_ENDPOINT ?? "http://127.0.0.1:11434";
|
|
21469
21486
|
var DEFAULT_SERVE_ENDPOINT = process.env.FREELLAMA_SERVE_ENDPOINT ?? "http://127.0.0.1:11435";
|
|
21470
|
-
var DEFAULT_DELEGATE_MODEL = process.env.FREELLAMA_MCP_DEFAULT_MODEL
|
|
21487
|
+
var DEFAULT_DELEGATE_MODEL = process.env.FREELLAMA_MCP_DEFAULT_MODEL?.trim() || void 0;
|
|
21471
21488
|
var DEFAULT_DELEGATE_MAX_TURNS = envInt("FREELLAMA_MCP_MAX_TURNS", 8);
|
|
21472
21489
|
var DEFAULT_DELEGATE_TIMEOUT_SECONDS = envInt("FREELLAMA_MCP_DELEGATE_TIMEOUT_SECONDS", 180);
|
|
21473
21490
|
var DEFAULT_PULL_TIMEOUT_SECONDS = envInt("FREELLAMA_MCP_PULL_TIMEOUT_SECONDS", 1200);
|
|
@@ -21481,7 +21498,7 @@ function parseAllowedResearchRoots(raw, delimiter = path.delimiter) {
|
|
|
21481
21498
|
return raw.split(delimiter).filter(Boolean).map((root) => path.resolve(root));
|
|
21482
21499
|
}
|
|
21483
21500
|
var ALLOWED_RESEARCH_ROOTS = parseAllowedResearchRoots(
|
|
21484
|
-
process.env.FREELLAMA_MCP_ALLOWED_ROOTS ??
|
|
21501
|
+
process.env.FREELLAMA_MCP_ALLOWED_ROOTS ?? CHECKOUT_ROOT ?? ""
|
|
21485
21502
|
);
|
|
21486
21503
|
var resolvedRootsPromise = null;
|
|
21487
21504
|
function allowedResearchRoots() {
|
|
@@ -21521,59 +21538,315 @@ async function assertAllowedWorkspace(workspacePath) {
|
|
|
21521
21538
|
}
|
|
21522
21539
|
return resolved;
|
|
21523
21540
|
}
|
|
21541
|
+
var DELEGATE_ENV_ALLOWLIST = /* @__PURE__ */ new Set([
|
|
21542
|
+
"PATH",
|
|
21543
|
+
"HOME",
|
|
21544
|
+
"USER",
|
|
21545
|
+
"LOGNAME",
|
|
21546
|
+
"LANG",
|
|
21547
|
+
"LC_ALL",
|
|
21548
|
+
"LC_CTYPE",
|
|
21549
|
+
"TZ",
|
|
21550
|
+
"TMPDIR",
|
|
21551
|
+
"TEMP",
|
|
21552
|
+
"TMP",
|
|
21553
|
+
"SYSTEMROOT",
|
|
21554
|
+
"SYSTEMDRIVE",
|
|
21555
|
+
"WINDIR",
|
|
21556
|
+
"COMSPEC",
|
|
21557
|
+
"PATHEXT",
|
|
21558
|
+
"APPDATA",
|
|
21559
|
+
"LOCALAPPDATA",
|
|
21560
|
+
"USERPROFILE",
|
|
21561
|
+
"HTTP_PROXY",
|
|
21562
|
+
"HTTPS_PROXY",
|
|
21563
|
+
"NO_PROXY",
|
|
21564
|
+
"http_proxy",
|
|
21565
|
+
"https_proxy",
|
|
21566
|
+
"no_proxy",
|
|
21567
|
+
"NODE_EXTRA_CA_CERTS",
|
|
21568
|
+
"SSL_CERT_FILE",
|
|
21569
|
+
"SSL_CERT_DIR",
|
|
21570
|
+
"REQUESTS_CA_BUNDLE",
|
|
21571
|
+
"XDG_CACHE_HOME",
|
|
21572
|
+
"XDG_DATA_HOME",
|
|
21573
|
+
"npm_config_cache"
|
|
21574
|
+
]);
|
|
21575
|
+
function delegateEnvironment(source = process.env) {
|
|
21576
|
+
const env = {};
|
|
21577
|
+
for (const [key, value] of Object.entries(source)) {
|
|
21578
|
+
if (value !== void 0 && (DELEGATE_ENV_ALLOWLIST.has(key) || key.startsWith("FREELLAMA_"))) env[key] = value;
|
|
21579
|
+
}
|
|
21580
|
+
return env;
|
|
21581
|
+
}
|
|
21524
21582
|
|
|
21525
|
-
// src/
|
|
21526
|
-
|
|
21527
|
-
|
|
21528
|
-
var
|
|
21529
|
-
var
|
|
21530
|
-
|
|
21531
|
-
|
|
21532
|
-
|
|
21533
|
-
|
|
21534
|
-
|
|
21535
|
-
|
|
21583
|
+
// src/serve.ts
|
|
21584
|
+
var HEALTH_PATH = "/_freellama/v1/health";
|
|
21585
|
+
var STARTUP_TIMEOUT_MS = 15e3;
|
|
21586
|
+
var child = null;
|
|
21587
|
+
var starting = null;
|
|
21588
|
+
function withServe(call) {
|
|
21589
|
+
return async (endpoint, ...args) => {
|
|
21590
|
+
await ensureServe(endpoint ?? void 0);
|
|
21591
|
+
return call(endpoint, ...args);
|
|
21592
|
+
};
|
|
21593
|
+
}
|
|
21594
|
+
function autostartEnabled() {
|
|
21595
|
+
return process.env.FREELLAMA_MCP_AUTOSTART_SERVE !== "0";
|
|
21596
|
+
}
|
|
21597
|
+
function loopbackListen(endpoint) {
|
|
21536
21598
|
try {
|
|
21537
|
-
|
|
21599
|
+
const url = new URL(endpoint);
|
|
21600
|
+
if (!["127.0.0.1", "localhost", "[::1]"].includes(url.hostname)) return null;
|
|
21601
|
+
return `${url.hostname === "localhost" ? "127.0.0.1" : url.hostname}:${url.port || "80"}`;
|
|
21538
21602
|
} catch {
|
|
21539
21603
|
return null;
|
|
21540
21604
|
}
|
|
21541
|
-
|
|
21542
|
-
|
|
21543
|
-
|
|
21544
|
-
|
|
21545
|
-
|
|
21546
|
-
|
|
21547
|
-
|
|
21548
|
-
|
|
21549
|
-
|
|
21605
|
+
}
|
|
21606
|
+
async function answers(endpoint, timeoutMs) {
|
|
21607
|
+
try {
|
|
21608
|
+
await fetch(`${endpoint.replace(/\/$/, "")}${HEALTH_PATH}`, { signal: AbortSignal.timeout(timeoutMs) });
|
|
21609
|
+
return true;
|
|
21610
|
+
} catch {
|
|
21611
|
+
return false;
|
|
21612
|
+
}
|
|
21613
|
+
}
|
|
21614
|
+
function serveBinaryCandidates() {
|
|
21615
|
+
const exe = process.platform === "win32" ? "freellama.exe" : "freellama";
|
|
21616
|
+
const candidates = [
|
|
21617
|
+
path2.join(REPO_ROOT, "target", "release", exe),
|
|
21618
|
+
path2.join(REPO_ROOT, "target", "debug", exe)
|
|
21619
|
+
];
|
|
21620
|
+
const requireFromHere = createRequire2(fileURLToPath2(import.meta.url));
|
|
21621
|
+
const suffixes = process.platform === "linux" ? ["-gnu", "-musl"] : process.platform === "win32" ? ["-msvc"] : [""];
|
|
21622
|
+
for (const suffix of suffixes) {
|
|
21550
21623
|
try {
|
|
21551
|
-
const
|
|
21552
|
-
|
|
21553
|
-
const withinRoot = relative && relative !== ".." && !relative.startsWith(`..${path2.sep}`);
|
|
21554
|
-
if (withinRoot && statSync(resolved).isFile()) return relative;
|
|
21624
|
+
const manifest = requireFromHere.resolve(`@octocodeai/freellama-native-${process.platform}-${process.arch}${suffix}/package.json`);
|
|
21625
|
+
candidates.push(path2.join(path2.dirname(manifest), exe));
|
|
21555
21626
|
} catch {
|
|
21556
21627
|
}
|
|
21557
21628
|
}
|
|
21558
|
-
|
|
21629
|
+
if (process.env.FREELLAMA_SERVE_BINARY) candidates.unshift(process.env.FREELLAMA_SERVE_BINARY);
|
|
21630
|
+
return candidates;
|
|
21631
|
+
}
|
|
21632
|
+
function stopAutostartedServe() {
|
|
21633
|
+
if (child && child.exitCode === null) child.kill("SIGTERM");
|
|
21634
|
+
child = null;
|
|
21635
|
+
}
|
|
21636
|
+
async function ensureServe(endpoint) {
|
|
21637
|
+
const target = endpoint ?? DEFAULT_SERVE_ENDPOINT;
|
|
21638
|
+
if (!autostartEnabled() || target !== DEFAULT_SERVE_ENDPOINT) return;
|
|
21639
|
+
const listen = loopbackListen(target);
|
|
21640
|
+
if (!listen) return;
|
|
21641
|
+
if (child && child.exitCode === null && await answers(target, 1e3)) return;
|
|
21642
|
+
starting ??= (async () => {
|
|
21643
|
+
try {
|
|
21644
|
+
if (await answers(target, 1e3)) return;
|
|
21645
|
+
const binary = serveBinaryCandidates().find((candidate) => existsSync2(candidate));
|
|
21646
|
+
if (!binary) return;
|
|
21647
|
+
const spawned = spawn(binary, ["serve", "--listen", listen, "--upstream", DEFAULT_OLLAMA_ENDPOINT], {
|
|
21648
|
+
stdio: ["ignore", "ignore", "pipe"]
|
|
21649
|
+
});
|
|
21650
|
+
child = spawned;
|
|
21651
|
+
spawned.stderr?.on("data", (chunk) => process.stderr.write(`[freellama serve] ${chunk}`));
|
|
21652
|
+
spawned.once("exit", () => {
|
|
21653
|
+
if (child === spawned) child = null;
|
|
21654
|
+
});
|
|
21655
|
+
await new Promise((resolve, reject) => {
|
|
21656
|
+
spawned.once("spawn", resolve);
|
|
21657
|
+
spawned.once("error", (error2) => {
|
|
21658
|
+
if (child === spawned) child = null;
|
|
21659
|
+
reject(new Error(`Could not start freellama serve at ${target}: ${error2.message}`));
|
|
21660
|
+
});
|
|
21661
|
+
});
|
|
21662
|
+
const deadline = Date.now() + STARTUP_TIMEOUT_MS;
|
|
21663
|
+
while (Date.now() < deadline && child === spawned) {
|
|
21664
|
+
if (await answers(target, 500)) return;
|
|
21665
|
+
await new Promise((resolve) => setTimeout(resolve, 200));
|
|
21666
|
+
}
|
|
21667
|
+
if (child === spawned) {
|
|
21668
|
+
stopAutostartedServe();
|
|
21669
|
+
throw new Error(`freellama serve did not become ready at ${target} within ${STARTUP_TIMEOUT_MS}ms.`);
|
|
21670
|
+
}
|
|
21671
|
+
throw new Error(`freellama serve exited before becoming ready at ${target} (exit ${spawned.exitCode}, signal ${spawned.signalCode}).`);
|
|
21672
|
+
} finally {
|
|
21673
|
+
starting = null;
|
|
21674
|
+
}
|
|
21675
|
+
})();
|
|
21676
|
+
await starting;
|
|
21677
|
+
}
|
|
21678
|
+
|
|
21679
|
+
// src/lifecycle.ts
|
|
21680
|
+
var liveDelegates = /* @__PURE__ */ new Set();
|
|
21681
|
+
var DETACH_DELEGATES = process.platform !== "win32";
|
|
21682
|
+
function killDelegateTree(child2) {
|
|
21683
|
+
if (DETACH_DELEGATES && child2.pid !== void 0) {
|
|
21684
|
+
try {
|
|
21685
|
+
process.kill(-child2.pid, "SIGKILL");
|
|
21686
|
+
return;
|
|
21687
|
+
} catch {
|
|
21688
|
+
}
|
|
21689
|
+
}
|
|
21690
|
+
try {
|
|
21691
|
+
child2.kill("SIGKILL");
|
|
21692
|
+
} catch {
|
|
21693
|
+
}
|
|
21694
|
+
}
|
|
21695
|
+
function runDelegate(file, args, options) {
|
|
21696
|
+
const child2 = spawn2(file, args, { env: options.env, detached: DETACH_DELEGATES, stdio: ["ignore", "pipe", "pipe"] });
|
|
21697
|
+
const done = new Promise((resolve, reject) => {
|
|
21698
|
+
const out = [];
|
|
21699
|
+
const err = [];
|
|
21700
|
+
let outLen = 0;
|
|
21701
|
+
let errLen = 0;
|
|
21702
|
+
let failure;
|
|
21703
|
+
let timedOut = false;
|
|
21704
|
+
let settled = false;
|
|
21705
|
+
const fail = (error2) => {
|
|
21706
|
+
if (failure === void 0) failure = error2;
|
|
21707
|
+
killDelegateTree(child2);
|
|
21708
|
+
};
|
|
21709
|
+
const timer = setTimeout(() => {
|
|
21710
|
+
timedOut = true;
|
|
21711
|
+
killDelegateTree(child2);
|
|
21712
|
+
}, options.timeoutMs);
|
|
21713
|
+
const onAbort = () => {
|
|
21714
|
+
const abort = new Error("The operation was aborted", { cause: options.signal?.reason });
|
|
21715
|
+
abort.name = "AbortError";
|
|
21716
|
+
abort.code = "ABORT_ERR";
|
|
21717
|
+
fail(abort);
|
|
21718
|
+
};
|
|
21719
|
+
if (options.signal?.aborted) onAbort();
|
|
21720
|
+
else options.signal?.addEventListener("abort", onAbort, { once: true });
|
|
21721
|
+
const collect = (chunks, name) => (chunk) => {
|
|
21722
|
+
const length = name === "stdout" ? outLen += chunk.length : errLen += chunk.length;
|
|
21723
|
+
if (length > options.maxBuffer) {
|
|
21724
|
+
const overflow = new RangeError(`${name} maxBuffer length exceeded`);
|
|
21725
|
+
overflow.code = "ERR_CHILD_PROCESS_STDIO_MAXBUFFER";
|
|
21726
|
+
fail(overflow);
|
|
21727
|
+
return;
|
|
21728
|
+
}
|
|
21729
|
+
chunks.push(chunk);
|
|
21730
|
+
};
|
|
21731
|
+
child2.stdout.on("data", collect(out, "stdout"));
|
|
21732
|
+
child2.stderr.on("data", collect(err, "stderr"));
|
|
21733
|
+
const finish = (code, signal, spawnError) => {
|
|
21734
|
+
if (settled) return;
|
|
21735
|
+
settled = true;
|
|
21736
|
+
clearTimeout(timer);
|
|
21737
|
+
options.signal?.removeEventListener("abort", onAbort);
|
|
21738
|
+
const stdout = Buffer.concat(out).toString("utf8");
|
|
21739
|
+
const stderr = Buffer.concat(err).toString("utf8");
|
|
21740
|
+
if (spawnError ?? failure) {
|
|
21741
|
+
reject(spawnError ?? failure);
|
|
21742
|
+
return;
|
|
21743
|
+
}
|
|
21744
|
+
if (code === 0 && signal === null) {
|
|
21745
|
+
resolve({ stdout, stderr });
|
|
21746
|
+
return;
|
|
21747
|
+
}
|
|
21748
|
+
const error2 = new Error(`Command failed: ${[file, ...args].join(" ")}
|
|
21749
|
+
${stderr}`);
|
|
21750
|
+
Object.assign(error2, { code, killed: timedOut, signal, stdout, stderr });
|
|
21751
|
+
reject(error2);
|
|
21752
|
+
};
|
|
21753
|
+
child2.once("error", (error2) => finish(null, null, error2));
|
|
21754
|
+
child2.once("close", (code, signal) => finish(code, signal));
|
|
21755
|
+
});
|
|
21756
|
+
return { child: child2, done };
|
|
21757
|
+
}
|
|
21758
|
+
function killLiveDelegates() {
|
|
21759
|
+
for (const child2 of liveDelegates) killDelegateTree(child2);
|
|
21760
|
+
liveDelegates.clear();
|
|
21761
|
+
}
|
|
21762
|
+
function installProcessLifecycle() {
|
|
21763
|
+
for (const stream of [process.stdout, process.stderr]) {
|
|
21764
|
+
stream.on("error", (error2) => {
|
|
21765
|
+
if (error2.code === "EPIPE") process.exit(0);
|
|
21766
|
+
throw error2;
|
|
21767
|
+
});
|
|
21768
|
+
}
|
|
21769
|
+
process.on("exit", () => {
|
|
21770
|
+
killLiveDelegates();
|
|
21771
|
+
stopAutostartedServe();
|
|
21772
|
+
});
|
|
21773
|
+
process.stdin.on("close", () => {
|
|
21774
|
+
killLiveDelegates();
|
|
21775
|
+
stopAutostartedServe();
|
|
21776
|
+
process.exit(0);
|
|
21777
|
+
});
|
|
21778
|
+
for (const signal of ["SIGINT", "SIGTERM", "SIGHUP"]) {
|
|
21779
|
+
process.on(signal, () => {
|
|
21780
|
+
killLiveDelegates();
|
|
21781
|
+
process.exit(0);
|
|
21782
|
+
});
|
|
21783
|
+
}
|
|
21784
|
+
}
|
|
21785
|
+
|
|
21786
|
+
// src/schema-dialect.ts
|
|
21787
|
+
function stripSchemaDialect(message) {
|
|
21788
|
+
const tools = message.result?.tools;
|
|
21789
|
+
if (!Array.isArray(tools)) return message;
|
|
21790
|
+
for (const tool of tools) {
|
|
21791
|
+
delete tool.inputSchema?.$schema;
|
|
21792
|
+
delete tool.outputSchema?.$schema;
|
|
21793
|
+
}
|
|
21794
|
+
return message;
|
|
21795
|
+
}
|
|
21796
|
+
function withoutSchemaDialect(transport2) {
|
|
21797
|
+
const send = transport2.send.bind(transport2);
|
|
21798
|
+
transport2.send = (message, options) => send(stripSchemaDialect(message), options);
|
|
21799
|
+
return transport2;
|
|
21800
|
+
}
|
|
21801
|
+
|
|
21802
|
+
// src/docs-resources.ts
|
|
21803
|
+
import { existsSync as existsSync3, readdirSync } from "node:fs";
|
|
21804
|
+
import { readFile } from "node:fs/promises";
|
|
21805
|
+
import path3 from "node:path";
|
|
21806
|
+
import { fileURLToPath as fileURLToPath3 } from "node:url";
|
|
21807
|
+
function registerDocsResources(server2) {
|
|
21808
|
+
const PACKAGED_DOCS_DIR = path3.resolve(path3.dirname(fileURLToPath3(import.meta.url)), "..", "docs");
|
|
21809
|
+
if (!existsSync3(path3.join(PACKAGED_DOCS_DIR, "README.md"))) {
|
|
21810
|
+
throw new Error(`FreeLlama MCP documentation index is missing at ${PACKAGED_DOCS_DIR}/README.md; run the package build.`);
|
|
21811
|
+
}
|
|
21812
|
+
const packagedDocs = readdirSync(PACKAGED_DOCS_DIR).filter((name) => name.endsWith(".md")).sort();
|
|
21813
|
+
for (const name of packagedDocs) {
|
|
21814
|
+
const isIndex = name === "README.md";
|
|
21815
|
+
const slug = isIndex ? "index" : name.replace(/\.md$/, "");
|
|
21816
|
+
const uri = `freellama://docs/${slug}`;
|
|
21817
|
+
server2.registerResource(
|
|
21818
|
+
`freellama-docs-${slug.toLowerCase()}`,
|
|
21819
|
+
uri,
|
|
21820
|
+
{
|
|
21821
|
+
mimeType: "text/markdown",
|
|
21822
|
+
description: isIndex ? "Index of packaged FreeLlama operator and agent documentation. Read this first, then fetch one relevant document." : `Packaged FreeLlama documentation: ${name}.`
|
|
21823
|
+
},
|
|
21824
|
+
async () => ({
|
|
21825
|
+
contents: [{ uri, mimeType: "text/markdown", text: await readFile(path3.join(PACKAGED_DOCS_DIR, name), "utf8") }]
|
|
21826
|
+
})
|
|
21827
|
+
);
|
|
21828
|
+
}
|
|
21559
21829
|
}
|
|
21560
|
-
|
|
21830
|
+
|
|
21831
|
+
// src/helpers.ts
|
|
21832
|
+
async function ollamaFetch(endpoint, path6, init = {}) {
|
|
21561
21833
|
const base = (endpoint ?? DEFAULT_OLLAMA_ENDPOINT).replace(/\/$/, "");
|
|
21562
21834
|
const controller = new AbortController();
|
|
21563
21835
|
const timeout = setTimeout(
|
|
21564
21836
|
() => controller.abort(),
|
|
21565
21837
|
init.timeoutMs ?? DEFAULT_OLLAMA_FETCH_TIMEOUT_SECONDS * 1e3
|
|
21566
21838
|
);
|
|
21839
|
+
const signal = init.signal ? AbortSignal.any([controller.signal, init.signal]) : controller.signal;
|
|
21567
21840
|
try {
|
|
21568
|
-
const response = await fetch(`${base}${
|
|
21841
|
+
const response = await fetch(`${base}${path6}`, {
|
|
21569
21842
|
method: init.method ?? "GET",
|
|
21570
21843
|
headers: init.body ? { "content-type": "application/json" } : void 0,
|
|
21571
21844
|
body: init.body ? JSON.stringify(init.body) : void 0,
|
|
21572
|
-
signal
|
|
21845
|
+
signal
|
|
21573
21846
|
});
|
|
21574
|
-
const text = await response.text();
|
|
21847
|
+
const text = init.onLine && response.ok && response.body ? await readNdjson(response.body, init.onLine) : await response.text();
|
|
21575
21848
|
if (!response.ok) {
|
|
21576
|
-
throw new Error(`Ollama ${init.method ?? "GET"} ${
|
|
21849
|
+
throw new Error(`Ollama ${init.method ?? "GET"} ${path6} -> HTTP ${response.status}: ${text}`);
|
|
21577
21850
|
}
|
|
21578
21851
|
if (!text) return {};
|
|
21579
21852
|
if (init.parse === "ndjson") return text;
|
|
@@ -21582,62 +21855,28 @@ async function ollamaFetch(endpoint, path5, init = {}) {
|
|
|
21582
21855
|
clearTimeout(timeout);
|
|
21583
21856
|
}
|
|
21584
21857
|
}
|
|
21585
|
-
function
|
|
21586
|
-
const
|
|
21587
|
-
|
|
21588
|
-
|
|
21589
|
-
|
|
21590
|
-
|
|
21591
|
-
|
|
21592
|
-
|
|
21593
|
-
|
|
21594
|
-
|
|
21595
|
-
|
|
21596
|
-
|
|
21597
|
-
|
|
21598
|
-
|
|
21599
|
-
|
|
21600
|
-
try {
|
|
21601
|
-
const parsed = JSON.parse(text);
|
|
21602
|
-
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
21603
|
-
return parsed;
|
|
21858
|
+
async function readNdjson(body, onLine) {
|
|
21859
|
+
const decoder = new TextDecoder();
|
|
21860
|
+
let text = "";
|
|
21861
|
+
let pending = "";
|
|
21862
|
+
for await (const chunk of body) {
|
|
21863
|
+
const piece = decoder.decode(chunk, { stream: true });
|
|
21864
|
+
text += piece;
|
|
21865
|
+
pending += piece;
|
|
21866
|
+
const lines = pending.split("\n");
|
|
21867
|
+
pending = lines.pop() ?? "";
|
|
21868
|
+
for (const line of lines) {
|
|
21869
|
+
try {
|
|
21870
|
+
const parsed = JSON.parse(line);
|
|
21871
|
+
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) onLine(parsed);
|
|
21872
|
+
} catch {
|
|
21604
21873
|
}
|
|
21605
|
-
} catch {
|
|
21606
|
-
return { status: "unparsed", raw: clipText(text, 500) };
|
|
21607
21874
|
}
|
|
21608
|
-
return { status: "unparsed", raw: clipText(text, 500) };
|
|
21609
21875
|
}
|
|
21610
|
-
|
|
21611
|
-
const withBytes = [...events].reverse().find((event) => typeof event.total === "number" && event.total > 0);
|
|
21612
|
-
const completed = typeof withBytes?.completed === "number" ? withBytes.completed : null;
|
|
21613
|
-
const total = typeof withBytes?.total === "number" ? withBytes.total : null;
|
|
21614
|
-
const percent = completed != null && total != null && total > 0 ? Math.round(completed / total * 1e3) / 10 : null;
|
|
21615
|
-
const status = typeof last.error === "string" && last.error ? "error" : last.status;
|
|
21616
|
-
return {
|
|
21617
|
-
...last,
|
|
21618
|
-
...status === void 0 ? {} : { status },
|
|
21619
|
-
progress: {
|
|
21620
|
-
events: events.length,
|
|
21621
|
-
completed,
|
|
21622
|
-
total,
|
|
21623
|
-
percent,
|
|
21624
|
-
lastStatus: last.status ?? null
|
|
21625
|
-
}
|
|
21626
|
-
};
|
|
21627
|
-
}
|
|
21628
|
-
async function ollamaPull(endpoint, model, timeoutSeconds) {
|
|
21629
|
-
const raw = await ollamaFetch(endpoint, "/api/pull", {
|
|
21630
|
-
method: "POST",
|
|
21631
|
-
body: { name: model, stream: true },
|
|
21632
|
-
timeoutMs: (timeoutSeconds ?? DEFAULT_PULL_TIMEOUT_SECONDS) * 1e3,
|
|
21633
|
-
parse: "ndjson"
|
|
21634
|
-
});
|
|
21635
|
-
if (typeof raw === "string") return summarizeOllamaPullStream(raw);
|
|
21636
|
-
if (raw && typeof raw === "object" && !Array.isArray(raw)) return raw;
|
|
21637
|
-
return { status: "empty" };
|
|
21876
|
+
return text + decoder.decode();
|
|
21638
21877
|
}
|
|
21639
|
-
var endpointParam = external_exports.string().min(1).optional()
|
|
21640
|
-
var ollamaEndpointParam = external_exports.string().min(1).optional()
|
|
21878
|
+
var endpointParam = external_exports.string().min(1).optional();
|
|
21879
|
+
var ollamaEndpointParam = external_exports.string().min(1).optional();
|
|
21641
21880
|
var TASK_KINDS = [
|
|
21642
21881
|
"completion",
|
|
21643
21882
|
"coding",
|
|
@@ -21649,28 +21888,14 @@ var TASK_KINDS = [
|
|
|
21649
21888
|
"long_context"
|
|
21650
21889
|
];
|
|
21651
21890
|
var MCP_TASK_KINDS = [...TASK_KINDS, "code_review"];
|
|
21652
|
-
var taskParam = external_exports.enum(MCP_TASK_KINDS).describe("
|
|
21891
|
+
var taskParam = external_exports.enum(MCP_TASK_KINDS).default("completion").describe("caller owns prompts and output format.");
|
|
21653
21892
|
function canonicalTaskKind(task) {
|
|
21654
21893
|
return task === "code_review" ? "coding" : task;
|
|
21655
21894
|
}
|
|
21656
|
-
var objectiveParam = external_exports.enum(["fastest", "balanced", "quality"]).optional()
|
|
21657
|
-
var executionPreferenceParam = external_exports.enum(["auto", "prefer_cpu", "prefer_gpu"]).optional()
|
|
21658
|
-
|
|
21659
|
-
);
|
|
21660
|
-
var minPlacementEvidenceParam = external_exports.enum(["configured", "observed"]).optional().describe('"observed" fails closed unless Ollama /api/ps confirms the selected processor');
|
|
21661
|
-
var CONFIDENCE_RANK = { low: 1, medium: 2 };
|
|
21662
|
-
var minConfidenceParam = external_exports.enum(["low", "medium"]).optional().describe('Fail closed below this. "medium" needs policy AND benchmark; "low" accepts capability metadata.');
|
|
21663
|
-
function belowConfidence(decision, minConfidence) {
|
|
21664
|
-
if (!minConfidence) return null;
|
|
21665
|
-
const actual = typeof decision.confidence === "string" ? decision.confidence : "low";
|
|
21666
|
-
if ((CONFIDENCE_RANK[actual] ?? 0) >= (CONFIDENCE_RANK[minConfidence] ?? 0)) return null;
|
|
21667
|
-
return errorResult(
|
|
21668
|
-
new Error(
|
|
21669
|
-
`Route refused: confidence is "${actual}" (evidence: ${decision.evidence}), below the requested minimum "${minConfidence}". Selected model would have been "${decision.selected_model}" for reasons [${decision.reasons?.join(", ")}].
|
|
21670
|
-
This is a fail-closed refusal, not a failure: the local router cannot justify this pick. Escalate to your own model, pass an explicit \`model\`, or run \`bench-all\` and configure a task policy to raise the evidence level.`
|
|
21671
|
-
)
|
|
21672
|
-
);
|
|
21673
|
-
}
|
|
21895
|
+
var objectiveParam = external_exports.enum(["fastest", "balanced", "quality"]).optional();
|
|
21896
|
+
var executionPreferenceParam = external_exports.enum(["auto", "prefer_cpu", "prefer_gpu"]).optional();
|
|
21897
|
+
var minPlacementEvidenceParam = external_exports.enum(["configured", "observed"]).optional();
|
|
21898
|
+
var minConfidenceParam = external_exports.enum(["low", "medium"]).optional();
|
|
21674
21899
|
var REQUIRED_CAPABILITIES = [
|
|
21675
21900
|
"completion",
|
|
21676
21901
|
"tools",
|
|
@@ -21679,22 +21904,51 @@ var REQUIRED_CAPABILITIES = [
|
|
|
21679
21904
|
"thinking",
|
|
21680
21905
|
"embedding"
|
|
21681
21906
|
];
|
|
21682
|
-
var requiredCapabilitiesParam = external_exports.array(external_exports.enum(REQUIRED_CAPABILITIES)).max(REQUIRED_CAPABILITIES.length).optional()
|
|
21907
|
+
var requiredCapabilitiesParam = external_exports.array(external_exports.enum(REQUIRED_CAPABILITIES)).max(REQUIRED_CAPABILITIES.length).optional();
|
|
21908
|
+
var systemPromptParam = external_exports.string().optional().describe("Local model instructions; verbatim.");
|
|
21909
|
+
var messagesParam = external_exports.array(external_exports.object({
|
|
21910
|
+
role: external_exports.enum(["system", "user", "assistant", "tool"]),
|
|
21911
|
+
content: external_exports.string().optional()
|
|
21912
|
+
}).passthrough()).min(1).optional();
|
|
21913
|
+
var localToolsParam = external_exports.array(external_exports.object({
|
|
21914
|
+
type: external_exports.literal("function"),
|
|
21915
|
+
function: external_exports.object({
|
|
21916
|
+
name: external_exports.string().min(1),
|
|
21917
|
+
description: external_exports.string().optional(),
|
|
21918
|
+
parameters: external_exports.record(external_exports.unknown()).optional()
|
|
21919
|
+
}).passthrough()
|
|
21920
|
+
}).passthrough()).min(1).optional();
|
|
21921
|
+
function taskMessages({ systemPrompt, messages, prompt, images }) {
|
|
21922
|
+
if (systemPrompt === void 0) return messages ?? [];
|
|
21923
|
+
const conversation = messages ?? (prompt === void 0 ? [] : [{
|
|
21924
|
+
role: "user",
|
|
21925
|
+
content: prompt,
|
|
21926
|
+
...images === void 0 ? {} : { images }
|
|
21927
|
+
}]);
|
|
21928
|
+
return [{ role: "system", content: systemPrompt }, ...conversation];
|
|
21929
|
+
}
|
|
21683
21930
|
var batchTaskParam = external_exports.object({
|
|
21684
|
-
task
|
|
21931
|
+
// Nested `task` shares the top-level description; repeating it re-sends the text every request.
|
|
21932
|
+
task: taskParam.removeDefault().optional(),
|
|
21685
21933
|
objective: objectiveParam,
|
|
21686
21934
|
model: external_exports.string().min(1).optional(),
|
|
21687
21935
|
sessionId: external_exports.string().uuid().optional(),
|
|
21936
|
+
scopeId: external_exports.string().uuid().optional(),
|
|
21937
|
+
scopeRevision: external_exports.number().int().nonnegative().optional(),
|
|
21688
21938
|
contextTokens: external_exports.number().int().positive().optional(),
|
|
21689
21939
|
executionPreference: executionPreferenceParam,
|
|
21690
21940
|
minPlacementEvidence: minPlacementEvidenceParam,
|
|
21691
21941
|
requiredCapabilities: requiredCapabilitiesParam,
|
|
21692
21942
|
priority: external_exports.enum(["interactive", "normal", "background"]).optional(),
|
|
21943
|
+
maxWaitSeconds: external_exports.number().int().positive().optional(),
|
|
21944
|
+
timeoutSeconds: external_exports.number().int().positive().optional(),
|
|
21945
|
+
stopOnRepetition: external_exports.boolean().optional().describe("Stream and stop once output repeats itself; keeps all generated text"),
|
|
21693
21946
|
prompt: external_exports.string().min(1).optional(),
|
|
21694
|
-
|
|
21947
|
+
systemPrompt: systemPromptParam,
|
|
21948
|
+
messages: messagesParam,
|
|
21695
21949
|
images: external_exports.array(external_exports.string().min(1)).min(1).optional(),
|
|
21696
21950
|
input: external_exports.union([external_exports.string().min(1), external_exports.array(external_exports.string().min(1)).min(1)]).optional(),
|
|
21697
|
-
tools:
|
|
21951
|
+
tools: localToolsParam,
|
|
21698
21952
|
keepAlive: external_exports.string().min(1).optional(),
|
|
21699
21953
|
format: external_exports.union([external_exports.literal("json"), external_exports.record(external_exports.unknown())]).optional(),
|
|
21700
21954
|
think: external_exports.union([external_exports.boolean(), external_exports.enum(["low", "medium", "high"])]).optional(),
|
|
@@ -21703,52 +21957,27 @@ var batchTaskParam = external_exports.object({
|
|
|
21703
21957
|
topLogprobs: external_exports.number().int().nonnegative().optional(),
|
|
21704
21958
|
minConfidence: minConfidenceParam
|
|
21705
21959
|
}).strict();
|
|
21960
|
+
function fresh(schema) {
|
|
21961
|
+
const def = { ...schema._def };
|
|
21962
|
+
for (const [key, value] of Object.entries(def)) {
|
|
21963
|
+
if (value instanceof external_exports.ZodType) def[key] = fresh(value);
|
|
21964
|
+
else if (Array.isArray(value) && value.length > 0 && value.every((v) => v instanceof external_exports.ZodType)) {
|
|
21965
|
+
def[key] = value.map((v) => fresh(v));
|
|
21966
|
+
}
|
|
21967
|
+
}
|
|
21968
|
+
if (schema instanceof external_exports.ZodObject) {
|
|
21969
|
+
const shape = Object.fromEntries(Object.entries(schema.shape).map(([k, v]) => [k, fresh(v)]));
|
|
21970
|
+
def.shape = () => shape;
|
|
21971
|
+
}
|
|
21972
|
+
return new schema.constructor(def);
|
|
21973
|
+
}
|
|
21974
|
+
var BATCH_TASK_FIELDS = new Set(Object.keys(batchTaskParam.shape));
|
|
21706
21975
|
var batchItemParam = external_exports.object({
|
|
21707
21976
|
id: external_exports.string().min(1),
|
|
21708
21977
|
independent: external_exports.literal(true),
|
|
21709
|
-
task: batchTaskParam
|
|
21978
|
+
task: fresh(batchTaskParam)
|
|
21710
21979
|
}).strict();
|
|
21711
21980
|
var objectResultSchema = external_exports.object({}).passthrough();
|
|
21712
|
-
var doctorResultSchema = external_exports.object({
|
|
21713
|
-
summary: external_exports.string().optional(),
|
|
21714
|
-
endpoint: external_exports.string().optional(),
|
|
21715
|
-
scheduler: external_exports.unknown().optional()
|
|
21716
|
-
}).passthrough();
|
|
21717
|
-
var sessionResultSchema = external_exports.object({
|
|
21718
|
-
session_id: external_exports.string().optional(),
|
|
21719
|
-
deleted: external_exports.boolean().optional()
|
|
21720
|
-
}).passthrough();
|
|
21721
|
-
var manageResultSchema = external_exports.object({
|
|
21722
|
-
status: external_exports.string().optional(),
|
|
21723
|
-
progress: external_exports.unknown().optional()
|
|
21724
|
-
}).passthrough();
|
|
21725
|
-
var deleteResultSchema = external_exports.object({
|
|
21726
|
-
deleted: external_exports.string()
|
|
21727
|
-
}).passthrough();
|
|
21728
|
-
var modelsResultSchema = external_exports.object({
|
|
21729
|
-
models: external_exports.array(external_exports.record(external_exports.unknown())).optional(),
|
|
21730
|
-
tags: external_exports.array(external_exports.record(external_exports.unknown())).optional(),
|
|
21731
|
-
page: external_exports.unknown().optional()
|
|
21732
|
-
}).passthrough();
|
|
21733
|
-
var taskResultSchema = external_exports.object({
|
|
21734
|
-
selected_model: external_exports.string().optional(),
|
|
21735
|
-
context_window_fit: external_exports.string().optional(),
|
|
21736
|
-
execution: external_exports.unknown().optional(),
|
|
21737
|
-
response: external_exports.unknown().optional(),
|
|
21738
|
-
telemetry: external_exports.unknown().optional()
|
|
21739
|
-
}).passthrough();
|
|
21740
|
-
var batchResultSchema = external_exports.object({
|
|
21741
|
-
results: external_exports.unknown().optional(),
|
|
21742
|
-
telemetry: external_exports.unknown().optional()
|
|
21743
|
-
}).passthrough();
|
|
21744
|
-
var researchResultSchema = external_exports.object({
|
|
21745
|
-
answer: external_exports.string(),
|
|
21746
|
-
summary: external_exports.string(),
|
|
21747
|
-
citations: external_exports.unknown().optional(),
|
|
21748
|
-
verification: external_exports.unknown(),
|
|
21749
|
-
usage: external_exports.unknown().optional(),
|
|
21750
|
-
telemetry: external_exports.unknown().optional()
|
|
21751
|
-
}).passthrough();
|
|
21752
21981
|
function configuredExternalCost(env = process.env) {
|
|
21753
21982
|
const model = env.FREELLAMA_EXTERNAL_COST_MODEL;
|
|
21754
21983
|
const inputRaw = env.FREELLAMA_EXTERNAL_COST_INPUT_USD_PER_M;
|
|
@@ -21763,22 +21992,22 @@ function configuredExternalCost(env = process.env) {
|
|
|
21763
21992
|
}
|
|
21764
21993
|
return { model, input, output };
|
|
21765
21994
|
}
|
|
21766
|
-
function costTelemetry(
|
|
21995
|
+
function costTelemetry(usage2, externalCost) {
|
|
21767
21996
|
const local = {
|
|
21768
|
-
inputTokens:
|
|
21769
|
-
outputTokens:
|
|
21770
|
-
totalTokens:
|
|
21771
|
-
totalDurationNs:
|
|
21997
|
+
inputTokens: usage2.inputTokens,
|
|
21998
|
+
outputTokens: usage2.outputTokens,
|
|
21999
|
+
totalTokens: usage2.inputTokens === null || usage2.outputTokens === null ? null : usage2.inputTokens + usage2.outputTokens,
|
|
22000
|
+
totalDurationNs: usage2.totalDurationNs ?? null
|
|
21772
22001
|
};
|
|
21773
|
-
if (!externalCost ||
|
|
22002
|
+
if (!externalCost || usage2.inputTokens === null || usage2.outputTokens === null) {
|
|
21774
22003
|
return {
|
|
21775
22004
|
local,
|
|
21776
22005
|
externalEquivalent: null,
|
|
21777
22006
|
note: externalCost ? "External equivalent unavailable because Ollama did not report both input and output token counts." : "No external rate card supplied; local usage is observed, but avoided external cost is not estimated."
|
|
21778
22007
|
};
|
|
21779
22008
|
}
|
|
21780
|
-
const inputUsd =
|
|
21781
|
-
const outputUsd =
|
|
22009
|
+
const inputUsd = usage2.inputTokens * externalCost.input / 1e6;
|
|
22010
|
+
const outputUsd = usage2.outputTokens * externalCost.output / 1e6;
|
|
21782
22011
|
return {
|
|
21783
22012
|
local,
|
|
21784
22013
|
externalEquivalent: {
|
|
@@ -21805,20 +22034,67 @@ function clipText(text, limit) {
|
|
|
21805
22034
|
const head = Math.floor(usable * 2 / 3);
|
|
21806
22035
|
return text.slice(0, head) + marker + text.slice(-(usable - head));
|
|
21807
22036
|
}
|
|
21808
|
-
var PRETTY_PRINT_MAX_BYTES = 8 * 1024;
|
|
21809
|
-
function serialize(value) {
|
|
21810
|
-
const compact = JSON.stringify(value);
|
|
21811
|
-
return compact.length > PRETTY_PRINT_MAX_BYTES ? compact : JSON.stringify(value, null, 2);
|
|
21812
|
-
}
|
|
21813
22037
|
function resultSummary(value) {
|
|
21814
22038
|
const explicit = value.summary;
|
|
21815
22039
|
if (typeof explicit === "string" && explicit.trim()) return clipText(explicit.trim(), 500);
|
|
21816
22040
|
const keys = Object.keys(value);
|
|
21817
22041
|
return `Structured result available (${keys.slice(0, 8).join(", ") || "empty object"}).`;
|
|
21818
22042
|
}
|
|
22043
|
+
var ANSWER_TEXT_MAX_CHARS = 8 * 1024;
|
|
22044
|
+
function record2(value) {
|
|
22045
|
+
return value && typeof value === "object" && !Array.isArray(value) ? value : void 0;
|
|
22046
|
+
}
|
|
22047
|
+
function taskAnswerText(payload) {
|
|
22048
|
+
const response = record2(payload.response);
|
|
22049
|
+
if (!response) return void 0;
|
|
22050
|
+
const model = record2(payload.route)?.selected_model ?? payload.selected_model ?? response.model;
|
|
22051
|
+
const header = typeof model === "string" && model ? `[${model}] ` : "";
|
|
22052
|
+
const message = record2(response.message);
|
|
22053
|
+
if (message) {
|
|
22054
|
+
const parts = [];
|
|
22055
|
+
if (typeof message.content === "string" && message.content.trim()) parts.push(answerContent(message.content, payload));
|
|
22056
|
+
if (Array.isArray(message.tool_calls) && message.tool_calls.length > 0) {
|
|
22057
|
+
parts.push(`tool_calls: ${JSON.stringify(message.tool_calls)}`);
|
|
22058
|
+
}
|
|
22059
|
+
if (parts.length === 0) parts.push("(empty response)");
|
|
22060
|
+
return clipText(header + parts.join("\n"), ANSWER_TEXT_MAX_CHARS);
|
|
22061
|
+
}
|
|
22062
|
+
if (typeof response.response === "string") return clipText(header + response.response.trim(), ANSWER_TEXT_MAX_CHARS);
|
|
22063
|
+
const omitted = record2(response.embeddings_omitted);
|
|
22064
|
+
if (Array.isArray(response.embeddings) || omitted) {
|
|
22065
|
+
const count = Array.isArray(response.embeddings) ? response.embeddings.length : omitted?.count;
|
|
22066
|
+
const where = omitted ? "vectors withheld; pass returnEmbeddings:true" : "vectors are in structuredContent";
|
|
22067
|
+
return `${header}embedding complete (${String(count ?? "?")} vectors); ${where}.`;
|
|
22068
|
+
}
|
|
22069
|
+
return void 0;
|
|
22070
|
+
}
|
|
22071
|
+
function answerContent(content, payload) {
|
|
22072
|
+
const check2 = record2(payload.output_check);
|
|
22073
|
+
const keep = check2?.unique_prefix_chars;
|
|
22074
|
+
if (check2?.repetition_loop !== true || typeof keep !== "number") return content.trim();
|
|
22075
|
+
const characters = Array.from(content);
|
|
22076
|
+
const trimmed = characters.slice(0, keep).join("").trim();
|
|
22077
|
+
return `${trimmed}
|
|
22078
|
+
[freellama: trimmed ${characters.length - keep} repeated characters; the model looped until num_predict \u2014 treat as low confidence]`;
|
|
22079
|
+
}
|
|
22080
|
+
function batchAnswerText(payload) {
|
|
22081
|
+
if (!Array.isArray(payload.results)) return void 0;
|
|
22082
|
+
const perItem = Math.max(512, Math.floor(ANSWER_TEXT_MAX_CHARS / Math.max(payload.results.length, 1)));
|
|
22083
|
+
const lines = payload.results.map((row) => {
|
|
22084
|
+
const item = record2(row) ?? {};
|
|
22085
|
+
const id = String(item.id ?? "?");
|
|
22086
|
+
if (item.ok !== true) return `## ${id} (failed)
|
|
22087
|
+
${clipText(JSON.stringify(item.error ?? item), perItem)}`;
|
|
22088
|
+
const answer = taskAnswerText(record2(item.response) ?? {}) ?? "(no answer)";
|
|
22089
|
+
return `## ${id}
|
|
22090
|
+
${clipText(answer, perItem)}`;
|
|
22091
|
+
});
|
|
22092
|
+
return lines.join("\n\n");
|
|
22093
|
+
}
|
|
21819
22094
|
function structuredResult(value, options = {}) {
|
|
22095
|
+
const text = options.text ?? resultSummary(value);
|
|
21820
22096
|
return {
|
|
21821
|
-
content: [{ type: "text", text
|
|
22097
|
+
content: [{ type: "text", text }],
|
|
21822
22098
|
structuredContent: value
|
|
21823
22099
|
};
|
|
21824
22100
|
}
|
|
@@ -21839,44 +22115,44 @@ ${clipText(raw, 2e3)}`
|
|
|
21839
22115
|
}
|
|
21840
22116
|
return structuredResult(value);
|
|
21841
22117
|
}
|
|
22118
|
+
function isStructuredSuccess(result) {
|
|
22119
|
+
return !("isError" in result && result.isError) && "structuredContent" in result && result.structuredContent !== void 0;
|
|
22120
|
+
}
|
|
22121
|
+
var escapeRegExp = (text) => text.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
22122
|
+
var SERVE_ADDRESS_PATTERN = (() => {
|
|
22123
|
+
let url;
|
|
22124
|
+
try {
|
|
22125
|
+
url = new URL(DEFAULT_SERVE_ENDPOINT);
|
|
22126
|
+
} catch {
|
|
22127
|
+
return /_freellama/i;
|
|
22128
|
+
}
|
|
22129
|
+
const { hostname: hostname2, port } = url;
|
|
22130
|
+
const hosts = ["127.0.0.1", "localhost"].includes(hostname2) ? ["127.0.0.1", "localhost"] : [hostname2];
|
|
22131
|
+
const addresses = hosts.map((host) => escapeRegExp(port ? `${host}:${port}` : host));
|
|
22132
|
+
return new RegExp(`(?:_freellama|${addresses.join("|")})`, "i");
|
|
22133
|
+
})();
|
|
21842
22134
|
function errorResult(error2) {
|
|
21843
22135
|
const message = error2 instanceof Error ? error2.message : String(error2);
|
|
21844
|
-
|
|
22136
|
+
let receipt;
|
|
22137
|
+
try {
|
|
22138
|
+
const parsed = JSON.parse(message);
|
|
22139
|
+
if (parsed !== null && typeof parsed === "object" && !Array.isArray(parsed) && typeof parsed.error === "string") {
|
|
22140
|
+
receipt = parsed;
|
|
22141
|
+
}
|
|
22142
|
+
} catch {
|
|
22143
|
+
}
|
|
22144
|
+
const managedServeUnavailable = /(?:connection refused|connect error|failed to connect|error sending request)/i.test(message) && SERVE_ADDRESS_PATTERN.test(message);
|
|
22145
|
+
const ollamaUnavailable = !managedServeUnavailable && /(?:connection refused|connect error|failed to connect|error sending request)/i.test(message) && /\/api\/(?:tags|ps|chat|generate|embed|show)/.test(message);
|
|
21845
22146
|
const actionableMessage = managedServeUnavailable ? `${message}
|
|
21846
22147
|
|
|
21847
|
-
FreeLlama managed serve is unreachable. Start it with \`freellama serve\`, or set \`FREELLAMA_SERVE_ENDPOINT\` to a running managed endpoint; then retry. Run \`doctor\` to inspect the configured endpoint.` : message
|
|
21848
|
-
|
|
21849
|
-
|
|
21850
|
-
|
|
21851
|
-
|
|
21852
|
-
|
|
21853
|
-
|
|
21854
|
-
|
|
21855
|
-
command: external_exports.string().optional(),
|
|
21856
|
-
queries: external_exports.object({ path: external_exports.string().optional() }).passthrough().optional()
|
|
21857
|
-
}).passthrough().optional()
|
|
21858
|
-
}).passthrough();
|
|
21859
|
-
var adapterResultSchema = external_exports.object({
|
|
21860
|
-
final_answer: external_exports.string(),
|
|
21861
|
-
tool_calls: external_exports.array(adapterCallSchema).default([]),
|
|
21862
|
-
usage: external_exports.object({
|
|
21863
|
-
input_tokens: external_exports.number().nullable().optional(),
|
|
21864
|
-
output_tokens: external_exports.number().nullable().optional()
|
|
21865
|
-
}).passthrough().default({}),
|
|
21866
|
-
model_metadata: external_exports.object({
|
|
21867
|
-
context_compactions: external_exports.number().int().nonnegative().optional(),
|
|
21868
|
-
runtime_config: external_exports.record(external_exports.string(), external_exports.unknown()).optional(),
|
|
21869
|
-
context_management: external_exports.object({
|
|
21870
|
-
token_counting: external_exports.enum(["configured_estimate", "model_calibrated_estimate"]),
|
|
21871
|
-
estimate_scale: external_exports.number().positive(),
|
|
21872
|
-
calibration_samples: external_exports.number().int().nonnegative(),
|
|
21873
|
-
pinned_overflow: external_exports.enum(["error", "clip"]),
|
|
21874
|
-
compactions: external_exports.number().int().nonnegative()
|
|
21875
|
-
}).passthrough().optional()
|
|
21876
|
-
}).passthrough().optional()
|
|
21877
|
-
});
|
|
21878
|
-
function parseAdapterResult(raw) {
|
|
21879
|
-
return adapterResultSchema.parse(JSON.parse(raw));
|
|
22148
|
+
FreeLlama managed serve is unreachable. Start it with \`freellama serve\`, or set \`FREELLAMA_SERVE_ENDPOINT\` to a running managed endpoint; then retry. Run \`doctor\` to inspect the configured endpoint.` : ollamaUnavailable ? `${message}
|
|
22149
|
+
|
|
22150
|
+
Ollama is not running at that address. Ask the user to start Ollama (the app, or \`ollama serve\`); retrying before that cannot succeed.` : message;
|
|
22151
|
+
return {
|
|
22152
|
+
content: [{ type: "text", text: actionableMessage }],
|
|
22153
|
+
isError: true,
|
|
22154
|
+
...receipt ? { structuredContent: receipt } : {}
|
|
22155
|
+
};
|
|
21880
22156
|
}
|
|
21881
22157
|
function summarizeEmbeddings(payload) {
|
|
21882
22158
|
const response = payload.response;
|
|
@@ -21899,13 +22175,211 @@ function summarizeEmbeddings(payload) {
|
|
|
21899
22175
|
};
|
|
21900
22176
|
}
|
|
21901
22177
|
|
|
22178
|
+
// src/tools/doctor.ts
|
|
22179
|
+
var doctorResultSchema = external_exports.object({
|
|
22180
|
+
summary: external_exports.string().optional(),
|
|
22181
|
+
endpoint: external_exports.string().optional()
|
|
22182
|
+
}).passthrough();
|
|
22183
|
+
function registerDoctorTool(server2) {
|
|
22184
|
+
server2.registerTool(
|
|
22185
|
+
"doctor",
|
|
22186
|
+
{
|
|
22187
|
+
description: "Use when: checking whether Ollama/FreeLlama are healthy, reading live queues/limits (view:status) or token usage (view:usage). Do not use when: choosing a model (models). Returns: a short summary; pick a view for detail.",
|
|
22188
|
+
inputSchema: external_exports.object({
|
|
22189
|
+
ollamaEndpoint: ollamaEndpointParam,
|
|
22190
|
+
serveEndpoint: endpointParam,
|
|
22191
|
+
view: external_exports.enum(["summary", "full", "status", "usage"]).optional(),
|
|
22192
|
+
days: external_exports.number().int().min(1).max(366).optional()
|
|
22193
|
+
}).strict(),
|
|
22194
|
+
outputSchema: doctorResultSchema,
|
|
22195
|
+
annotations: { readOnlyHint: true }
|
|
22196
|
+
},
|
|
22197
|
+
async ({ ollamaEndpoint, serveEndpoint, view, days }) => {
|
|
22198
|
+
try {
|
|
22199
|
+
if (days !== void 0 && view !== "usage") return errorResult(new Error("days is only valid with view: usage."));
|
|
22200
|
+
if (view === "status") return parsedResult(await status(serveEndpoint));
|
|
22201
|
+
if (view === "usage") return parsedResult(await usage(serveEndpoint, days));
|
|
22202
|
+
const report = parsedResult(await doctor(ollamaEndpoint));
|
|
22203
|
+
if (!isStructuredSuccess(report)) return report;
|
|
22204
|
+
try {
|
|
22205
|
+
report.structuredContent.machine = JSON.parse(await machine(serveEndpoint));
|
|
22206
|
+
delete report.structuredContent.machine_unavailable;
|
|
22207
|
+
} catch (error2) {
|
|
22208
|
+
if (report.structuredContent.machine == null) {
|
|
22209
|
+
report.structuredContent.machine_unavailable = `freellama serve unreachable, so no machine profile: ${error2 instanceof Error ? error2.message : String(error2)}`;
|
|
22210
|
+
}
|
|
22211
|
+
}
|
|
22212
|
+
try {
|
|
22213
|
+
report.structuredContent.platform_health = JSON.parse(await health(serveEndpoint));
|
|
22214
|
+
} catch (error2) {
|
|
22215
|
+
report.structuredContent.platform_health_unavailable = `freellama serve unreachable: ${error2 instanceof Error ? error2.message : String(error2)}`;
|
|
22216
|
+
}
|
|
22217
|
+
const full = { ...report.structuredContent };
|
|
22218
|
+
delete full.ollama_env_config;
|
|
22219
|
+
if ((view ?? "summary") === "full") return structuredResult(full);
|
|
22220
|
+
const running = full.running?.models ?? [];
|
|
22221
|
+
const compact = {
|
|
22222
|
+
status: "ok",
|
|
22223
|
+
summary: `Ollama ${String(full.version?.version ?? "unknown")}; ${running.length} resident model(s).`,
|
|
22224
|
+
endpoint: full.endpoint,
|
|
22225
|
+
ollama: { endpoint: full.endpoint, version: full.version, resident_model_count: running.length },
|
|
22226
|
+
machine: full.machine,
|
|
22227
|
+
local_conservative_config_posture: full.local_conservative_config_posture,
|
|
22228
|
+
host_runtime_signals: full.host_runtime_signals,
|
|
22229
|
+
evidence: { runtime: "ollama_api_version_and_ps", configuration: full.ollama_env_config_source },
|
|
22230
|
+
next: 'Use models{view:"installed"} to select a local model; view:"full" has Ollama configuration and admission.'
|
|
22231
|
+
};
|
|
22232
|
+
return structuredResult(compact);
|
|
22233
|
+
} catch (error2) {
|
|
22234
|
+
return errorResult(error2);
|
|
22235
|
+
}
|
|
22236
|
+
}
|
|
22237
|
+
);
|
|
22238
|
+
}
|
|
22239
|
+
|
|
22240
|
+
// src/serve-client.ts
|
|
22241
|
+
var createSession2 = withServe(createSession);
|
|
22242
|
+
var deleteSession2 = withServe(deleteSession);
|
|
22243
|
+
var killSession2 = withServe(killSession);
|
|
22244
|
+
var listModels2 = withServe(listModels);
|
|
22245
|
+
var route2 = withServe(route);
|
|
22246
|
+
var runTaskRequest2 = withServe(runTaskRequest);
|
|
22247
|
+
var runTaskBatchRequest2 = withServe(runTaskBatchRequest);
|
|
22248
|
+
var warmModelRequest2 = withServe(warmModelRequest);
|
|
22249
|
+
var listTaskJobs2 = withServe(listTaskJobs);
|
|
22250
|
+
var getTaskJob2 = withServe(getTaskJob);
|
|
22251
|
+
var cancelTaskJob2 = withServe(cancelTaskJob);
|
|
22252
|
+
var removeTaskJob2 = withServe(removeTaskJob);
|
|
22253
|
+
var createScope2 = withServe(createScope);
|
|
22254
|
+
var getScope2 = withServe(getScope);
|
|
22255
|
+
var forkScope2 = withServe(forkScope);
|
|
22256
|
+
var deleteScope2 = withServe(deleteScope);
|
|
22257
|
+
|
|
22258
|
+
// src/tools/session.ts
|
|
22259
|
+
var sessionResultSchema = external_exports.object({
|
|
22260
|
+
session_id: external_exports.string().optional(),
|
|
22261
|
+
deleted: external_exports.boolean().optional(),
|
|
22262
|
+
killed: external_exports.boolean().optional(),
|
|
22263
|
+
runner_stop_confirmed: external_exports.boolean().optional()
|
|
22264
|
+
}).passthrough();
|
|
22265
|
+
function registerSessionTool(server2) {
|
|
22266
|
+
server2.registerTool(
|
|
22267
|
+
"session",
|
|
22268
|
+
{
|
|
22269
|
+
description: "Use when: several related run_task calls should stay on one model (create, pass sessionId), or to kill a session's in-flight work. Do not use when: cancelling one deferred job (task_jobs), keeping history (scope), or unloading (ollama_manage). Returns: receipt.",
|
|
22270
|
+
inputSchema: external_exports.object({
|
|
22271
|
+
action: external_exports.enum(["create", "delete", "kill"]),
|
|
22272
|
+
sessionId: external_exports.string().uuid().optional(),
|
|
22273
|
+
endpoint: endpointParam
|
|
22274
|
+
}).strict(),
|
|
22275
|
+
outputSchema: sessionResultSchema,
|
|
22276
|
+
annotations: { destructiveHint: true }
|
|
22277
|
+
},
|
|
22278
|
+
async ({ action, sessionId, endpoint }) => {
|
|
22279
|
+
try {
|
|
22280
|
+
if (action === "create") {
|
|
22281
|
+
if (sessionId !== void 0) return errorResult(new Error("sessionId is only valid for action: delete or kill."));
|
|
22282
|
+
return structuredResult(JSON.parse(await createSession2(endpoint)));
|
|
22283
|
+
}
|
|
22284
|
+
if (sessionId === void 0) return errorResult(new Error(`action: ${action} requires sessionId.`));
|
|
22285
|
+
if (action === "kill") return parsedResult(await killSession2(endpoint, sessionId));
|
|
22286
|
+
await deleteSession2(endpoint, sessionId);
|
|
22287
|
+
return structuredResult({ session_id: sessionId, deleted: true });
|
|
22288
|
+
} catch (error2) {
|
|
22289
|
+
return errorResult(error2);
|
|
22290
|
+
}
|
|
22291
|
+
}
|
|
22292
|
+
);
|
|
22293
|
+
}
|
|
22294
|
+
|
|
22295
|
+
// src/paging.ts
|
|
22296
|
+
import { createHash } from "node:crypto";
|
|
22297
|
+
function pageLiveList(items, limit, cursor, identity) {
|
|
22298
|
+
const pageSize = limit ?? 20;
|
|
22299
|
+
const fingerprint = createHash("sha256").update(items.map(identity).join("\n")).digest("base64url").slice(0, 16);
|
|
22300
|
+
let offset = 0;
|
|
22301
|
+
if (cursor) {
|
|
22302
|
+
try {
|
|
22303
|
+
const parsed = JSON.parse(Buffer.from(cursor, "base64url").toString("utf8"));
|
|
22304
|
+
if (!Number.isInteger(parsed.offset) || parsed.offset < 0 || parsed.fingerprint !== fingerprint) {
|
|
22305
|
+
throw new Error("invalid or stale cursor");
|
|
22306
|
+
}
|
|
22307
|
+
offset = parsed.offset;
|
|
22308
|
+
} catch {
|
|
22309
|
+
throw new Error("`cursor` is invalid or the model list changed; restart without cursor.");
|
|
22310
|
+
}
|
|
22311
|
+
}
|
|
22312
|
+
const page = items.slice(offset, offset + pageSize);
|
|
22313
|
+
const nextOffset = offset + page.length;
|
|
22314
|
+
return {
|
|
22315
|
+
items: page,
|
|
22316
|
+
returned: page.length,
|
|
22317
|
+
total: items.length,
|
|
22318
|
+
next_cursor: nextOffset < items.length ? Buffer.from(JSON.stringify({ offset: nextOffset, fingerprint }), "utf8").toString("base64url") : null
|
|
22319
|
+
};
|
|
22320
|
+
}
|
|
22321
|
+
|
|
21902
22322
|
// src/model-search.ts
|
|
22323
|
+
function decodeHtml(text) {
|
|
22324
|
+
const entities = { amp: "&", lt: "<", gt: ">", quot: '"', apos: "'", nbsp: " ", middot: "\xB7", bull: "\u2022" };
|
|
22325
|
+
return text.replace(/&(#x[\da-f]+|#\d+|[a-z]+);/gi, (raw, entity) => {
|
|
22326
|
+
if (!entity.startsWith("#")) return entities[entity.toLowerCase()] ?? raw;
|
|
22327
|
+
const code = entity[1].toLowerCase() === "x" ? Number.parseInt(entity.slice(2), 16) : Number(entity.slice(1));
|
|
22328
|
+
return code > 0 && code <= 1114111 && !(code >= 55296 && code <= 57343) ? String.fromCodePoint(code) : raw;
|
|
22329
|
+
});
|
|
22330
|
+
}
|
|
22331
|
+
function attribute(tag, name) {
|
|
22332
|
+
return tag.match(new RegExp(`(?:^|\\s)${name}\\s*=\\s*(["'])([\\s\\S]*?)\\1`, "i"))?.[2];
|
|
22333
|
+
}
|
|
22334
|
+
function htmlText(html) {
|
|
22335
|
+
return decodeHtml(html.replace(/<(script|style|svg)\b[^>]*>[\s\S]*?<\/\1>/gi, "").replace(/<\/(?:p|li|div|h[1-6]|pre|tr)>|<br\s*\/?\s*>/gi, "\n").replace(/<[^>]*>/g, " ")).split("\n").map((line) => line.replace(/\s+/g, " ").trim()).filter(Boolean).join("\n");
|
|
22336
|
+
}
|
|
22337
|
+
function divContent(html, id) {
|
|
22338
|
+
const tags = /<\/?div\b[^>]*>/gi;
|
|
22339
|
+
let start;
|
|
22340
|
+
let depth = 0;
|
|
22341
|
+
for (const match of html.matchAll(tags)) {
|
|
22342
|
+
if (start === void 0) {
|
|
22343
|
+
if (attribute(match[0], "id") !== id) continue;
|
|
22344
|
+
start = match.index + match[0].length;
|
|
22345
|
+
depth = 1;
|
|
22346
|
+
} else {
|
|
22347
|
+
depth += /^<\//.test(match[0]) ? -1 : 1;
|
|
22348
|
+
if (depth === 0) return html.slice(start, match.index);
|
|
22349
|
+
}
|
|
22350
|
+
}
|
|
22351
|
+
return void 0;
|
|
22352
|
+
}
|
|
22353
|
+
function parseModelCard(html) {
|
|
22354
|
+
const meta = [...html.matchAll(/<meta\b[^>]*>/gi)].find((m) => attribute(m[0], "name") === "description");
|
|
22355
|
+
const description = decodeHtml(meta ? attribute(meta[0], "content") ?? "" : "").trim();
|
|
22356
|
+
const editor = [...html.matchAll(/<textarea\b([^>]*)>([\s\S]*?)<\/textarea>/gi)].find((m) => attribute(m[1], "id") === "editor");
|
|
22357
|
+
const rendered = divContent(html, "display");
|
|
22358
|
+
const readme = editor ? decodeHtml(editor[2]).trim() : rendered === void 0 ? "" : htmlText(rendered);
|
|
22359
|
+
if (!description && !editor && rendered === void 0) throw new Error("Ollama library markup was not recognized.");
|
|
22360
|
+
const header = html.split(/<div[^>]*\bid=["']readme["']/i)[0];
|
|
22361
|
+
const capabilities = [...new Set([...header.matchAll(/<span\b[^>]*class=["'][^"']*text-(?:indigo-600|cyan-500)[^"']*["'][^>]*>\s*([a-z]+)\s*<\/span>/g)].map((m) => m[1]))];
|
|
22362
|
+
return { description, readme, readmeFormat: editor ? "markdown" : "text", capabilities };
|
|
22363
|
+
}
|
|
22364
|
+
function libraryTrialCandidate(tag, embedding) {
|
|
22365
|
+
return {
|
|
22366
|
+
tag,
|
|
22367
|
+
evidence: "download_size_preflight_only",
|
|
22368
|
+
taskQuality: "unmeasured",
|
|
22369
|
+
why: embedding ? "Smallest listed download inside the conservative host-memory budget. This is a trial candidate; smaller size does not establish better embedding quality." : "Largest listed download inside the conservative host-memory budget. This is a trial candidate; download size establishes neither accelerator fit nor task quality.",
|
|
22370
|
+
configure: 'Set contextTokens and output limits for the workload, then inspect preview and execution receipts. Ollama defaults depend on its configuration and hardware. Use keepAlive:"0" for one-off work; keep related repeated work warm when resources permit.',
|
|
22371
|
+
caution: "Measure this exact tag on representative tasks before choosing it. Include resident runners, context/KV caches and OS headroom in memory fit. Discovery does not authorize a pull."
|
|
22372
|
+
};
|
|
22373
|
+
}
|
|
21903
22374
|
function parseModelSearch(html) {
|
|
21904
22375
|
const results = [];
|
|
21905
22376
|
for (const block of html.split(/<li\s/).slice(1)) {
|
|
21906
|
-
const
|
|
22377
|
+
const link = block.match(/href="\/(?:library\/([\w.-]+)|([\w.-]+\/[\w.-]+))"/);
|
|
22378
|
+
const name = link?.[1] ?? link?.[2];
|
|
21907
22379
|
if (!name) continue;
|
|
21908
|
-
|
|
22380
|
+
if (!/<p\b[^>]*class="max-w-lg/.test(block)) continue;
|
|
22381
|
+
const rawDescription = block.match(/<p class="max-w-lg[^"]*">([\s\S]*?)<\/p>/)?.[1];
|
|
22382
|
+
const description = rawDescription === void 0 ? "" : decodeHtml(rawDescription.replace(/<[^>]+>/g, "")).replace(/\s+/g, " ").trim();
|
|
21909
22383
|
const capabilities = [...block.matchAll(/text-(?:indigo-600|cyan-500)[^>]*>([a-z]+)<\/span>/g)].map(
|
|
21910
22384
|
(m) => m[1]
|
|
21911
22385
|
);
|
|
@@ -21918,7 +22392,7 @@ function parseModelSearch(html) {
|
|
|
21918
22392
|
capabilities,
|
|
21919
22393
|
pulls: stat("Pulls"),
|
|
21920
22394
|
tags: stat("Tag"),
|
|
21921
|
-
|
|
22395
|
+
cloudAvailable: capabilities.includes("cloud")
|
|
21922
22396
|
});
|
|
21923
22397
|
}
|
|
21924
22398
|
return results;
|
|
@@ -21926,1046 +22400,1545 @@ function parseModelSearch(html) {
|
|
|
21926
22400
|
function parseModelTags(html, family) {
|
|
21927
22401
|
const tags = [];
|
|
21928
22402
|
const seen = /* @__PURE__ */ new Set();
|
|
21929
|
-
const row = /<a
|
|
22403
|
+
const row = /<a\b([^>]*)>([\s\S]*?)<\/a>/gi;
|
|
21930
22404
|
for (const m of html.matchAll(row)) {
|
|
21931
|
-
|
|
22405
|
+
let tag;
|
|
22406
|
+
try {
|
|
22407
|
+
tag = decodeURIComponent(attribute(m[1], "href") ?? "").replace(/^\/(?:library\/)?/, "");
|
|
22408
|
+
} catch {
|
|
22409
|
+
continue;
|
|
22410
|
+
}
|
|
22411
|
+
if (!tag.startsWith(`${family}:`)) continue;
|
|
22412
|
+
const text = htmlText(m[2]).replace(/\n/g, " ");
|
|
22413
|
+
if (!/context window|[\d.]\s*[MG]B\b|\bcloud\b/i.test(text)) continue;
|
|
21932
22414
|
if (seen.has(tag)) continue;
|
|
21933
22415
|
seen.add(tag);
|
|
21934
|
-
const meta =
|
|
22416
|
+
const meta = text.split(/[·•]/).map((x) => x.trim());
|
|
21935
22417
|
const sizeText = meta.find((x) => /^[\d.]+\s?[MG]B$/i.test(x)) ?? null;
|
|
21936
22418
|
const bytes = sizeText ? Number.parseFloat(sizeText) * (/GB/i.test(sizeText) ? 1e9 : 1e6) : null;
|
|
21937
22419
|
tags.push({
|
|
21938
22420
|
tag,
|
|
22421
|
+
digest: text.replace(tag, "").match(/\b[\da-f]{12,64}\b/i)?.[0]?.toLowerCase() ?? null,
|
|
22422
|
+
cloud: /-cloud$/i.test(tag) || meta.some((part) => /^cloud$/i.test(part)),
|
|
21939
22423
|
size: sizeText,
|
|
21940
22424
|
sizeBytes: bytes,
|
|
21941
22425
|
context: meta.find((x) => /context window/i.test(x))?.replace(/\s*context window\s*/i, "") ?? null,
|
|
21942
|
-
modalities: meta.find((x) => /^(Text|Image|Audio)/i.test(x)) ?? null,
|
|
22426
|
+
modalities: meta.find((x) => /^(Text|Image|Audio)/i.test(x))?.replace(/\s+input.*$/i, "") ?? null,
|
|
21943
22427
|
updated: meta.at(-1) ?? null
|
|
21944
22428
|
});
|
|
21945
22429
|
}
|
|
21946
22430
|
return { family, tags };
|
|
21947
22431
|
}
|
|
21948
22432
|
|
|
21949
|
-
// src/
|
|
21950
|
-
|
|
21951
|
-
|
|
21952
|
-
var
|
|
21953
|
-
|
|
21954
|
-
|
|
21955
|
-
|
|
21956
|
-
|
|
21957
|
-
|
|
21958
|
-
|
|
22433
|
+
// src/model-knowledge.ts
|
|
22434
|
+
var CACHE_TTL_MS = 60 * 60 * 1e3;
|
|
22435
|
+
var FAILURE_TTL_MS = 60 * 1e3;
|
|
22436
|
+
var MAX_PAGE_BYTES = 2 * 1024 * 1024;
|
|
22437
|
+
var MAX_README_CHARS = 64 * 1024;
|
|
22438
|
+
var errorMessage = (error2) => error2 instanceof Error ? error2.message : String(error2);
|
|
22439
|
+
function isRemoteModel(entry, name) {
|
|
22440
|
+
return Boolean(entry.remote_host || entry.remote_model) || /-cloud$/i.test(name);
|
|
22441
|
+
}
|
|
22442
|
+
function libraryReference(model) {
|
|
22443
|
+
const match = model.match(/^(?:library\/)?([a-zA-Z0-9][\w.-]*(?:\/[a-zA-Z0-9][\w.-]*)?)(?::([\w.-]+))?$/);
|
|
22444
|
+
if (!match || match[1].split("/").some((part) => part === "." || part === "..")) return null;
|
|
22445
|
+
const family = match[1];
|
|
22446
|
+
const pathname = family.includes("/") ? family : `library/${family}`;
|
|
22447
|
+
return { family, tag: `${family}:${match[2] ?? "latest"}`, url: `https://ollama.com/${pathname}` };
|
|
22448
|
+
}
|
|
22449
|
+
var LibraryFetchError = class extends Error {
|
|
22450
|
+
constructor(status2, message) {
|
|
22451
|
+
super(message);
|
|
22452
|
+
this.status = status2;
|
|
22453
|
+
}
|
|
22454
|
+
status;
|
|
22455
|
+
};
|
|
22456
|
+
var OllamaLibraryClient = class {
|
|
22457
|
+
constructor(options = {}) {
|
|
22458
|
+
this.options = options;
|
|
22459
|
+
}
|
|
22460
|
+
options;
|
|
22461
|
+
cache = /* @__PURE__ */ new Map();
|
|
22462
|
+
pending = /* @__PURE__ */ new Map();
|
|
22463
|
+
async search(params) {
|
|
22464
|
+
const url = `https://ollama.com/search?${params.toString()}`;
|
|
22465
|
+
const models = parseModelSearch(await this.page(url));
|
|
22466
|
+
return { url, models, fetchedAt: new Date((this.options.now ?? Date.now)()).toISOString() };
|
|
22467
|
+
}
|
|
22468
|
+
async lookup(model) {
|
|
22469
|
+
const ref = libraryReference(model);
|
|
22470
|
+
if (!ref) return {
|
|
22471
|
+
status: "unmatched",
|
|
22472
|
+
sourceUrl: null,
|
|
22473
|
+
tagsSourceUrl: null,
|
|
22474
|
+
fetchedAt: null,
|
|
22475
|
+
expiresAt: null,
|
|
22476
|
+
cached: false,
|
|
22477
|
+
message: "No exact Ollama public name could be resolved. Base-model provenance is unknown."
|
|
22478
|
+
};
|
|
22479
|
+
const now = this.options.now ?? Date.now;
|
|
22480
|
+
const cached2 = this.cache.get(ref.family);
|
|
22481
|
+
if (cached2 && Date.parse(cached2.expiresAt) > now()) {
|
|
22482
|
+
this.cache.delete(ref.family);
|
|
22483
|
+
this.cache.set(ref.family, cached2);
|
|
22484
|
+
return { ...cached2, cached: true };
|
|
22485
|
+
}
|
|
22486
|
+
const pending = this.pending.get(ref.family);
|
|
22487
|
+
if (pending) return { ...await pending, cached: true };
|
|
22488
|
+
const work = this.load(ref).then((record3) => {
|
|
22489
|
+
this.cache.delete(ref.family);
|
|
22490
|
+
this.cache.set(ref.family, record3);
|
|
22491
|
+
while (this.cache.size > (this.options.maxEntries ?? 128)) this.cache.delete(this.cache.keys().next().value);
|
|
22492
|
+
return record3;
|
|
22493
|
+
});
|
|
22494
|
+
this.pending.set(ref.family, work);
|
|
21959
22495
|
try {
|
|
21960
|
-
|
|
21961
|
-
|
|
21962
|
-
|
|
21963
|
-
} catch {
|
|
22496
|
+
return await work;
|
|
22497
|
+
} finally {
|
|
22498
|
+
this.pending.delete(ref.family);
|
|
21964
22499
|
}
|
|
21965
22500
|
}
|
|
21966
|
-
|
|
21967
|
-
|
|
21968
|
-
|
|
21969
|
-
|
|
21970
|
-
|
|
21971
|
-
|
|
21972
|
-
|
|
21973
|
-
|
|
21974
|
-
|
|
21975
|
-
|
|
21976
|
-
|
|
21977
|
-
|
|
21978
|
-
|
|
21979
|
-
|
|
21980
|
-
|
|
21981
|
-
|
|
21982
|
-
|
|
21983
|
-
|
|
21984
|
-
|
|
21985
|
-
|
|
21986
|
-
|
|
21987
|
-
|
|
21988
|
-
|
|
21989
|
-
|
|
21990
|
-
|
|
21991
|
-
|
|
21992
|
-
|
|
21993
|
-
|
|
21994
|
-
|
|
21995
|
-
|
|
21996
|
-
|
|
21997
|
-
|
|
21998
|
-
|
|
21999
|
-
|
|
22000
|
-
|
|
22001
|
-
};
|
|
22002
|
-
}
|
|
22003
|
-
const judgmentSignals = /\b(should (we|i|you)|better|best|worth|review|assess|evaluate|improve|opinion|recommend|why is|is it (good|safe|correct)|design|refactor)\b/i;
|
|
22004
|
-
if (judgmentSignals.test(question)) {
|
|
22005
|
-
return {
|
|
22006
|
-
recommendation: "verify",
|
|
22007
|
-
grounded: true,
|
|
22008
|
-
why: "The question reads as a judgment call (keyword heuristic), which is the ~67%-accurate bucket rather than the 98.9% one \u2014 and the tone is identical either way. Check the evidence trail against the claim before acting on it.",
|
|
22009
|
-
measuredBaseRate: `${model}: ~67% on judgment calls vs 98.9% on grounded lookups`
|
|
22010
|
-
};
|
|
22501
|
+
async page(url) {
|
|
22502
|
+
const response = await (this.options.fetch ?? fetch)(url, {
|
|
22503
|
+
method: "GET",
|
|
22504
|
+
redirect: "error",
|
|
22505
|
+
headers: { accept: "text/html" },
|
|
22506
|
+
signal: AbortSignal.timeout(this.options.timeoutMs ?? Math.min(DEFAULT_OLLAMA_FETCH_TIMEOUT_SECONDS * 1e3, 1e4))
|
|
22507
|
+
});
|
|
22508
|
+
if (!response.ok) {
|
|
22509
|
+
await response.body?.cancel();
|
|
22510
|
+
throw new LibraryFetchError(response.status === 404 ? "not_found" : "unavailable", `Ollama library returned HTTP ${response.status}.`);
|
|
22511
|
+
}
|
|
22512
|
+
if (!(response.headers.get("content-type") ?? "").includes("text/html")) {
|
|
22513
|
+
await response.body?.cancel();
|
|
22514
|
+
throw new LibraryFetchError("parse_error", "Ollama library did not return HTML.");
|
|
22515
|
+
}
|
|
22516
|
+
if (Number(response.headers.get("content-length")) > MAX_PAGE_BYTES) {
|
|
22517
|
+
await response.body?.cancel();
|
|
22518
|
+
throw new LibraryFetchError("unavailable", "Ollama library page exceeds the 2 MiB limit.");
|
|
22519
|
+
}
|
|
22520
|
+
const reader = response.body?.getReader();
|
|
22521
|
+
if (!reader) throw new LibraryFetchError("parse_error", "Ollama library returned an empty body.");
|
|
22522
|
+
const chunks = [];
|
|
22523
|
+
let bytes = 0;
|
|
22524
|
+
try {
|
|
22525
|
+
while (true) {
|
|
22526
|
+
const { done, value } = await reader.read();
|
|
22527
|
+
if (done) break;
|
|
22528
|
+
bytes += value.byteLength;
|
|
22529
|
+
if (bytes > MAX_PAGE_BYTES) throw new LibraryFetchError("unavailable", "Ollama library page exceeds the 2 MiB limit.");
|
|
22530
|
+
chunks.push(value);
|
|
22531
|
+
}
|
|
22532
|
+
return Buffer.concat(chunks).toString("utf8");
|
|
22533
|
+
} finally {
|
|
22534
|
+
await reader.cancel();
|
|
22535
|
+
}
|
|
22011
22536
|
}
|
|
22012
|
-
|
|
22537
|
+
async load(ref) {
|
|
22538
|
+
let result;
|
|
22539
|
+
try {
|
|
22540
|
+
const pages = await Promise.allSettled([this.page(ref.url), this.page(`${ref.url}/tags`)]);
|
|
22541
|
+
if (pages[0].status === "rejected") throw pages[0].reason;
|
|
22542
|
+
if (pages[1].status === "rejected") throw pages[1].reason;
|
|
22543
|
+
let card;
|
|
22544
|
+
try {
|
|
22545
|
+
card = parseModelCard(pages[0].value);
|
|
22546
|
+
} catch {
|
|
22547
|
+
throw new LibraryFetchError("parse_error", "Ollama model-card markup was not recognized.");
|
|
22548
|
+
}
|
|
22549
|
+
const { tags } = parseModelTags(pages[1].value, ref.family);
|
|
22550
|
+
if (tags.length === 0) throw new LibraryFetchError("parse_error", "No tag rows were recognized; check the source page before concluding a model is absent.");
|
|
22551
|
+
result = {
|
|
22552
|
+
status: "available",
|
|
22553
|
+
tags,
|
|
22554
|
+
readmeTruncated: card.readme.length > MAX_README_CHARS,
|
|
22555
|
+
card: { ...card, readme: card.readme.slice(0, MAX_README_CHARS) }
|
|
22556
|
+
};
|
|
22557
|
+
} catch (error2) {
|
|
22558
|
+
result = {
|
|
22559
|
+
status: error2 instanceof LibraryFetchError ? error2.status : "unavailable",
|
|
22560
|
+
message: error2 instanceof LibraryFetchError ? error2.message : `Ollama library lookup failed or timed out (${errorMessage(error2)}). Local metadata remains available.`
|
|
22561
|
+
};
|
|
22562
|
+
}
|
|
22563
|
+
const now = (this.options.now ?? Date.now)();
|
|
22013
22564
|
return {
|
|
22014
|
-
|
|
22015
|
-
|
|
22016
|
-
|
|
22017
|
-
|
|
22565
|
+
...result,
|
|
22566
|
+
sourceUrl: ref.url,
|
|
22567
|
+
tagsSourceUrl: `${ref.url}/tags`,
|
|
22568
|
+
fetchedAt: new Date(now).toISOString(),
|
|
22569
|
+
expiresAt: new Date(now + (result.status === "available" ? CACHE_TTL_MS : FAILURE_TTL_MS)).toISOString(),
|
|
22570
|
+
cached: false
|
|
22018
22571
|
};
|
|
22019
22572
|
}
|
|
22573
|
+
};
|
|
22574
|
+
var supportedUses = {
|
|
22575
|
+
completion: { use: "text_generation", reason: "Ollama advertises completion support; task accuracy still needs evaluation." },
|
|
22576
|
+
tools: { use: "tool_calling", reason: "Ollama advertises tool-call support; this does not establish reliable agent execution." },
|
|
22577
|
+
vision: { use: "image_understanding", reason: "Ollama advertises image-input support; OCR and visual accuracy are unmeasured here." },
|
|
22578
|
+
audio: { use: "audio_input", reason: "Ollama advertises audio support; inspect the model instructions for the supported audio workflow." },
|
|
22579
|
+
embedding: { use: "embeddings", reason: "Ollama advertises embedding support for retrieval and similarity; retrieval quality needs evaluation." },
|
|
22580
|
+
thinking: { use: "thinking", reason: "Ollama advertises a thinking mode; it does not establish reasoning accuracy." },
|
|
22581
|
+
insert: { use: "fill_in_middle", reason: "Ollama advertises suffix-based insertion support." },
|
|
22582
|
+
image: { use: "image_generation", reason: "Ollama advertises image-generation support." }
|
|
22583
|
+
};
|
|
22584
|
+
var advertisedUses = [
|
|
22585
|
+
{ task: "coding", pattern: /\b(coding|code generation|software engineering)\b/i, requires: "completion" },
|
|
22586
|
+
{ task: "tool_calling", pattern: /\b(tool (?:use|calling)|function calling)\b/i, requires: "tools" },
|
|
22587
|
+
{ task: "image_understanding", pattern: /\b(vision|image understanding|processing text and images|multimodal|OCR|optical character recognition)\b/i, requires: "vision" },
|
|
22588
|
+
{ task: "reasoning", pattern: /\b(reasoning|mathematical|math tasks)\b/i, requires: "completion" },
|
|
22589
|
+
{ task: "embeddings", pattern: /\b(embedding|embeddings|text encoder)\b/i, requires: "embedding" },
|
|
22590
|
+
{ task: "summarization", pattern: /\b(summarization|summarisation|summarize|summarise)\b/i, requires: "completion" },
|
|
22591
|
+
{ task: "question_answering", pattern: /\b(question answering|question-answering)\b/i, requires: "completion" },
|
|
22592
|
+
{ task: "translation", pattern: /\b(translation|translate)\b/i, requires: "completion" },
|
|
22593
|
+
{ task: "long_context", pattern: /\b(long[- ]context|repository[- ]scale understanding)\b/i, requires: "completion" }
|
|
22594
|
+
];
|
|
22595
|
+
function prose(card) {
|
|
22596
|
+
return `${card.description}
|
|
22597
|
+
${card.readme}`.replace(/```[\s\S]*?```|~~~[\s\S]*?~~~/g, "").replace(/<script\b[^>]*>[\s\S]*?<\/script>/gi, "").replace(/!\[[^\]]*\]\([^)]*\)/g, "").replace(/\[([^\]]+)\]\([^)]*\)/g, "$1").split(/\n|(?<=[.!?])\s+/).map((line) => line.replace(/^[\s#>*-]+/, "").trim()).filter(Boolean);
|
|
22598
|
+
}
|
|
22599
|
+
function publicModelGuidance(record3, capabilities, includeReadme = false) {
|
|
22600
|
+
const card = record3.card;
|
|
22601
|
+
if (!card) return { advertisedUseCases: [], usageNotes: [] };
|
|
22602
|
+
const sentences = prose(card);
|
|
22603
|
+
const advertisedUseCases = advertisedUses.flatMap(({ task, pattern, requires }) => {
|
|
22604
|
+
if (capabilities && !capabilities.includes(requires)) return [];
|
|
22605
|
+
const sentence = sentences.find((sentence2) => pattern.test(sentence2) && !/\b(not|never|cannot|unsupported|without)\b|doesn['’]t/i.test(sentence2));
|
|
22606
|
+
const start = sentence ? Math.max(0, sentence.search(pattern) - 120) : 0;
|
|
22607
|
+
return sentence ? [{
|
|
22608
|
+
task,
|
|
22609
|
+
basis: "advertised",
|
|
22610
|
+
scope: "family",
|
|
22611
|
+
quality: "unmeasured",
|
|
22612
|
+
evidence: { excerpt: sentence.slice(start, start + 320), sourceUrl: record3.sourceUrl, fetchedAt: record3.fetchedAt }
|
|
22613
|
+
}] : [];
|
|
22614
|
+
});
|
|
22615
|
+
const usageNotes = sentences.filter((sentence) => /\b(requires?|only|cannot|limitations?|must|memory|languages?|prefix)\b|not support|context (window|length)/i.test(sentence)).slice(0, 8).map((text) => ({ text: text.slice(0, 400), scope: "family", sourceUrl: record3.sourceUrl }));
|
|
22020
22616
|
return {
|
|
22021
|
-
|
|
22022
|
-
|
|
22023
|
-
|
|
22024
|
-
|
|
22617
|
+
description: card.description,
|
|
22618
|
+
advertisedFamilyCapabilities: card.capabilities,
|
|
22619
|
+
advertisedUseCases,
|
|
22620
|
+
usageNotes,
|
|
22621
|
+
readmeAvailable: card.readme.length > 0,
|
|
22622
|
+
...includeReadme ? { readme: { text: card.readme, format: card.readmeFormat, truncated: record3.readmeTruncated ?? false } } : {}
|
|
22025
22623
|
};
|
|
22026
22624
|
}
|
|
22027
|
-
|
|
22028
|
-
|
|
22029
|
-
|
|
22030
|
-
var EXTERNAL_COST = configuredExternalCost();
|
|
22031
|
-
function withTaskTelemetry(result) {
|
|
22032
|
-
if (!("structuredContent" in result)) return result;
|
|
22033
|
-
const payload = result.structuredContent;
|
|
22034
|
-
const metrics = payload.metrics;
|
|
22035
|
-
return structuredResult({
|
|
22036
|
-
...payload,
|
|
22037
|
-
telemetry: costTelemetry({
|
|
22038
|
-
inputTokens: typeof metrics?.prompt_tokens === "number" ? metrics.prompt_tokens : null,
|
|
22039
|
-
outputTokens: typeof metrics?.output_tokens === "number" ? metrics.output_tokens : null,
|
|
22040
|
-
totalDurationNs: typeof metrics?.total_duration_ns === "number" ? metrics.total_duration_ns : null
|
|
22041
|
-
}, EXTERNAL_COST)
|
|
22042
|
-
});
|
|
22625
|
+
function provenance(record3) {
|
|
22626
|
+
const { card: _card, tags: _tags, readmeTruncated: _truncated, ...source } = record3;
|
|
22627
|
+
return source;
|
|
22043
22628
|
}
|
|
22044
|
-
function
|
|
22045
|
-
|
|
22046
|
-
const
|
|
22047
|
-
const
|
|
22048
|
-
|
|
22049
|
-
|
|
22050
|
-
|
|
22051
|
-
|
|
22052
|
-
|
|
22053
|
-
|
|
22054
|
-
|
|
22055
|
-
|
|
22056
|
-
|
|
22057
|
-
|
|
22058
|
-
|
|
22059
|
-
|
|
22060
|
-
|
|
22061
|
-
|
|
22062
|
-
|
|
22629
|
+
function modelKnowledge(model, data, record3, includeReadme = false, supportedFeaturesSource = "ollama_api") {
|
|
22630
|
+
const capabilities = Array.isArray(data.capabilities) ? data.capabilities.filter((item) => typeof item === "string") : [];
|
|
22631
|
+
const supportedUseCases = capabilities.flatMap((capability) => Object.hasOwn(supportedUses, capability) ? [{ capability, ...supportedUses[capability], basis: "supported_feature" }] : []);
|
|
22632
|
+
const local = {
|
|
22633
|
+
supportedFeatures: capabilities,
|
|
22634
|
+
supportedFeaturesSource,
|
|
22635
|
+
supportedUseCases,
|
|
22636
|
+
quality: "metadata_only",
|
|
22637
|
+
note: "Supported features and advertised uses do not establish task quality. Keep exact-model benchmark and policy evidence separate."
|
|
22638
|
+
};
|
|
22639
|
+
if (isRemoteModel(data, model)) {
|
|
22640
|
+
return { ...local, localModel: false, library: { status: "remote_model", message: "This Ollama entry points to a remote model." } };
|
|
22641
|
+
}
|
|
22642
|
+
if (!record3) return { ...local, localModel: true, library: { status: "not_requested" } };
|
|
22643
|
+
if (record3.status !== "available") return { ...local, localModel: true, library: provenance(record3) };
|
|
22644
|
+
const ref = libraryReference(model);
|
|
22645
|
+
const tag = record3.tags?.find((tag2) => tag2.tag === ref.tag);
|
|
22646
|
+
const digest = typeof data.digest === "string" ? data.digest.replace(/^sha256:/, "").toLowerCase() : null;
|
|
22647
|
+
const validDigest = digest && /^[\da-f]{64}$/.test(digest);
|
|
22648
|
+
const matched = validDigest && tag?.digest && digest.startsWith(tag.digest);
|
|
22649
|
+
const status2 = !tag ? "tag_not_found" : !validDigest || !tag.digest ? "unverified" : matched ? "matched" : "digest_mismatch";
|
|
22650
|
+
if (!matched || tag?.cloud) return { ...local, localModel: true, library: {
|
|
22651
|
+
...provenance(record3),
|
|
22652
|
+
status: tag?.cloud ? "remote_tag" : status2,
|
|
22653
|
+
message: "Public guidance was withheld because an exact local tag and digest match could not be verified.",
|
|
22654
|
+
tag: tag ?? null
|
|
22655
|
+
} };
|
|
22656
|
+
return { ...local, localModel: true, library: {
|
|
22657
|
+
...provenance(record3),
|
|
22658
|
+
status: status2,
|
|
22659
|
+
tag,
|
|
22660
|
+
...publicModelGuidance(record3, capabilities, includeReadme)
|
|
22661
|
+
} };
|
|
22662
|
+
}
|
|
22663
|
+
async function enrichInstalledModels(models, client, includeLibrary, loadTags) {
|
|
22664
|
+
const results = new Array(models.length);
|
|
22665
|
+
const inventories = /* @__PURE__ */ new Map();
|
|
22666
|
+
let next = 0;
|
|
22667
|
+
await Promise.all(Array.from({ length: Math.min(4, models.length) }, async () => {
|
|
22668
|
+
while (next < models.length) {
|
|
22669
|
+
const index = next++;
|
|
22670
|
+
const model = models[index];
|
|
22671
|
+
const name = String(model.name ?? model.model ?? "");
|
|
22672
|
+
let local = model;
|
|
22673
|
+
let featureSource = "freellama_catalog";
|
|
22674
|
+
let localError;
|
|
22675
|
+
if (includeLibrary && loadTags) {
|
|
22676
|
+
const upstream = model.execution?.upstream;
|
|
22677
|
+
let inventory = inventories.get(upstream);
|
|
22678
|
+
if (!inventory) {
|
|
22679
|
+
inventory = loadTags(upstream);
|
|
22680
|
+
inventories.set(upstream, inventory);
|
|
22681
|
+
}
|
|
22682
|
+
try {
|
|
22683
|
+
const entry = (await inventory).find((entry2) => String(entry2.name ?? entry2.model) === name);
|
|
22684
|
+
if (!entry || entry.digest !== model.digest) localError = "Local inventory changed; inspect models again before matching public guidance.";
|
|
22685
|
+
else {
|
|
22686
|
+
local = { ...model, ...entry };
|
|
22687
|
+
if (Array.isArray(entry.capabilities)) featureSource = "ollama_api";
|
|
22688
|
+
}
|
|
22689
|
+
} catch (error2) {
|
|
22690
|
+
localError = `The selected Ollama backend's inventory could not be verified (${errorMessage(error2)}); public guidance was withheld.`;
|
|
22691
|
+
}
|
|
22692
|
+
}
|
|
22693
|
+
const record3 = includeLibrary && !localError && !isRemoteModel(local, name) ? await client.lookup(name) : void 0;
|
|
22694
|
+
const knowledge = modelKnowledge(name, local, record3, false, featureSource);
|
|
22695
|
+
results[index] = { ...model, knowledge: localError ? { ...knowledge, library: { status: "unverified", message: localError } } : knowledge };
|
|
22063
22696
|
}
|
|
22064
|
-
|
|
22065
|
-
|
|
22066
|
-
return structuredResult({
|
|
22067
|
-
...payload,
|
|
22068
|
-
results,
|
|
22069
|
-
telemetry: complete === rows.filter((row) => row?.ok === true).length ? costTelemetry({ inputTokens, outputTokens }, EXTERNAL_COST) : { local: null, externalEquivalent: null, note: "Batch aggregate unavailable because one or more successful items omitted token counts." }
|
|
22070
|
-
});
|
|
22697
|
+
}));
|
|
22698
|
+
return results;
|
|
22071
22699
|
}
|
|
22072
|
-
|
|
22073
|
-
|
|
22074
|
-
|
|
22075
|
-
|
|
22076
|
-
|
|
22700
|
+
|
|
22701
|
+
// src/tools/models.ts
|
|
22702
|
+
var modelsResultSchema = external_exports.object({
|
|
22703
|
+
models: external_exports.array(external_exports.record(external_exports.unknown())).optional(),
|
|
22704
|
+
tags: external_exports.array(external_exports.record(external_exports.unknown())).optional(),
|
|
22705
|
+
page: external_exports.unknown().optional(),
|
|
22706
|
+
knowledge: external_exports.unknown().optional(),
|
|
22707
|
+
library: external_exports.unknown().optional()
|
|
22708
|
+
}).passthrough();
|
|
22709
|
+
var ollamaLibrary = new OllamaLibraryClient();
|
|
22710
|
+
var HOST_MEMORY_FIT_FRACTION = 0.6;
|
|
22711
|
+
async function libraryLookup({
|
|
22712
|
+
model,
|
|
22713
|
+
query,
|
|
22714
|
+
capabilities,
|
|
22715
|
+
order,
|
|
22716
|
+
limit,
|
|
22717
|
+
cursor,
|
|
22718
|
+
includeReadme,
|
|
22719
|
+
endpoint,
|
|
22720
|
+
ollamaEndpoint
|
|
22721
|
+
}) {
|
|
22722
|
+
const localState = async () => {
|
|
22723
|
+
let installed = null;
|
|
22724
|
+
let installedUnavailable;
|
|
22725
|
+
let memoryBytes = null;
|
|
22077
22726
|
try {
|
|
22078
|
-
const
|
|
22079
|
-
|
|
22080
|
-
|
|
22081
|
-
}
|
|
22082
|
-
|
|
22727
|
+
const tags = await ollamaFetch(ollamaEndpoint, "/api/tags");
|
|
22728
|
+
installed = new Set((tags.models ?? []).map((m) => m.name ?? ""));
|
|
22729
|
+
} catch (error2) {
|
|
22730
|
+
installedUnavailable = `Local inventory could not be read (${error2 instanceof Error ? error2.message : String(error2)}); installed is null.`;
|
|
22731
|
+
}
|
|
22732
|
+
try {
|
|
22733
|
+
const profile = JSON.parse(await machine(endpoint));
|
|
22734
|
+
memoryBytes = profile.memory_bytes ?? profile.unified_memory_bytes ?? null;
|
|
22083
22735
|
} catch {
|
|
22084
|
-
throw new Error("`cursor` is invalid or the model list changed; restart without cursor.");
|
|
22085
22736
|
}
|
|
22737
|
+
return { installed, installedUnavailable, memoryBytes };
|
|
22738
|
+
};
|
|
22739
|
+
if (!model && cursor !== void 0) {
|
|
22740
|
+
return errorResult(new Error("Library step 1 has no `cursor`; it pages step-2 tags. Use `limit` for search, or pass `model`."));
|
|
22086
22741
|
}
|
|
22087
|
-
const page = items.slice(offset, offset + pageSize);
|
|
22088
|
-
const nextOffset = offset + page.length;
|
|
22089
|
-
return {
|
|
22090
|
-
items: page,
|
|
22091
|
-
returned: page.length,
|
|
22092
|
-
total: items.length,
|
|
22093
|
-
next_cursor: nextOffset < items.length ? Buffer.from(JSON.stringify({ offset: nextOffset, fingerprint }), "utf8").toString("base64url") : null
|
|
22094
|
-
};
|
|
22095
|
-
}
|
|
22096
|
-
var liveDelegates = /* @__PURE__ */ new Set();
|
|
22097
|
-
function killLiveDelegates() {
|
|
22098
|
-
for (const child of liveDelegates) {
|
|
22099
|
-
try {
|
|
22100
|
-
child.kill("SIGKILL");
|
|
22101
|
-
} catch {
|
|
22102
|
-
}
|
|
22103
|
-
}
|
|
22104
|
-
liveDelegates.clear();
|
|
22105
|
-
}
|
|
22106
|
-
for (const stream of [process.stdout, process.stderr]) {
|
|
22107
|
-
stream.on("error", (error2) => {
|
|
22108
|
-
if (error2.code === "EPIPE") process.exit(0);
|
|
22109
|
-
throw error2;
|
|
22110
|
-
});
|
|
22111
|
-
}
|
|
22112
|
-
process.on("exit", killLiveDelegates);
|
|
22113
|
-
for (const signal of ["SIGINT", "SIGTERM", "SIGHUP"]) {
|
|
22114
|
-
process.on(signal, () => {
|
|
22115
|
-
killLiveDelegates();
|
|
22116
|
-
process.exit(0);
|
|
22117
|
-
});
|
|
22118
|
-
}
|
|
22119
|
-
var INSTRUCTIONS = `caller owns task decomposition; operator owns endpoints, exact --cpu-model assignments, lifecycle.
|
|
22120
|
-
Ollama plus the OS/driver run physical CPU/GPU.
|
|
22121
|
-
Efficient loop: models{view:"installed"}, then models{view:"resident"}; doctor only for runtime diagnosis. Preview consequential work before executing.
|
|
22122
|
-
delegate_research is only for narrow allowed-workspace research. Keep full diagnostics, raw embeddings, and long evidence out of active context; read freellama://docs/index on demand.
|
|
22123
|
-
ask approval for one exact tag and reported size before ollama_manage; search or recommendation is never download permission.
|
|
22124
|
-
run_task preview never executes; code_review aliases coding.
|
|
22125
|
-
Use requiredCapabilities:["tools"] to preview tool eligibility; omit preview and supply the payload to execute.
|
|
22126
|
-
Docs: freellama://docs/index.`;
|
|
22127
|
-
var server = new McpServer(
|
|
22128
|
-
{ name: "freellama", version: SERVER_VERSION },
|
|
22129
|
-
{ instructions: INSTRUCTIONS }
|
|
22130
|
-
);
|
|
22131
|
-
var PACKAGED_DOCS_DIR = path4.resolve(path4.dirname(fileURLToPath2(import.meta.url)), "..", "docs");
|
|
22132
|
-
if (!existsSync4(PACKAGED_DOCS_DIR)) {
|
|
22133
|
-
throw new Error(`FreeLlama MCP documentation is missing at ${PACKAGED_DOCS_DIR}; run the package build.`);
|
|
22134
|
-
}
|
|
22135
|
-
var packagedDocs = readdirSync(PACKAGED_DOCS_DIR).filter((name) => name.endsWith(".md")).sort();
|
|
22136
|
-
if (!packagedDocs.includes("INDEX.md")) {
|
|
22137
|
-
throw new Error(`FreeLlama MCP documentation index is missing at ${PACKAGED_DOCS_DIR}/INDEX.md; run the package build.`);
|
|
22138
|
-
}
|
|
22139
|
-
for (const name of packagedDocs) {
|
|
22140
|
-
const uri = `freellama://docs/${name === "INDEX.md" ? "index" : name.replace(/\.md$/, "")}`;
|
|
22141
|
-
server.registerResource(
|
|
22142
|
-
`freellama-docs-${name.toLowerCase().replace(/\.md$/, "")}`,
|
|
22143
|
-
uri,
|
|
22144
|
-
{
|
|
22145
|
-
mimeType: "text/markdown",
|
|
22146
|
-
description: name === "INDEX.md" ? "Index of packaged FreeLlama operator and agent documentation. Read this first, then fetch one relevant document." : `Packaged FreeLlama documentation: ${name}.`
|
|
22147
|
-
},
|
|
22148
|
-
async () => ({
|
|
22149
|
-
contents: [{ uri, mimeType: "text/markdown", text: await readFile(path4.join(PACKAGED_DOCS_DIR, name), "utf8") }]
|
|
22150
|
-
})
|
|
22151
|
-
);
|
|
22152
|
-
}
|
|
22153
|
-
server.registerTool(
|
|
22154
|
-
"doctor",
|
|
22155
|
-
{
|
|
22156
|
-
description: "Use when: runtime/config diagnosis. Do not use when: model selection. Returns: compact summary by default; config/full are opt-in.",
|
|
22157
|
-
inputSchema: {
|
|
22158
|
-
endpoint: ollamaEndpointParam,
|
|
22159
|
-
serveEndpoint: endpointParam,
|
|
22160
|
-
view: external_exports.enum(["summary", "scheduler", "config", "full"]).optional().describe("summary default; scheduler/config/full are verbose")
|
|
22161
|
-
},
|
|
22162
|
-
outputSchema: doctorResultSchema,
|
|
22163
|
-
annotations: { readOnlyHint: true }
|
|
22164
|
-
},
|
|
22165
|
-
async ({ endpoint, serveEndpoint, view }) => {
|
|
22166
|
-
try {
|
|
22167
|
-
const report = parsedResult(await doctor(endpoint));
|
|
22168
|
-
if (!("structuredContent" in report)) return report;
|
|
22169
|
-
try {
|
|
22170
|
-
report.structuredContent.machine = JSON.parse(await machine(serveEndpoint));
|
|
22171
|
-
delete report.structuredContent.machine_unavailable;
|
|
22172
|
-
} catch (error2) {
|
|
22173
|
-
if (report.structuredContent.machine == null) {
|
|
22174
|
-
report.structuredContent.machine_unavailable = `freellama serve unreachable, so no machine profile: ${error2 instanceof Error ? error2.message : String(error2)}`;
|
|
22175
|
-
}
|
|
22176
|
-
}
|
|
22177
|
-
try {
|
|
22178
|
-
report.structuredContent.platform_health = JSON.parse(await health(serveEndpoint));
|
|
22179
|
-
} catch (error2) {
|
|
22180
|
-
report.structuredContent.platform_health_unavailable = `freellama serve unreachable: ${error2 instanceof Error ? error2.message : String(error2)}`;
|
|
22181
|
-
}
|
|
22182
|
-
const full = { ...report.structuredContent };
|
|
22183
|
-
const flatConfig = full.ollama_env_config;
|
|
22184
|
-
delete full.ollama_env_config;
|
|
22185
|
-
if (view === "config") {
|
|
22186
|
-
return structuredResult({
|
|
22187
|
-
status: "ok",
|
|
22188
|
-
summary: "Categorized Ollama configuration; values are source-qualified, not endpoint/PID proof.",
|
|
22189
|
-
endpoint: full.endpoint,
|
|
22190
|
-
ollama_config: full.ollama_config,
|
|
22191
|
-
ollama_env_config_source: full.ollama_env_config_source,
|
|
22192
|
-
ollama_env_config_warning: full.ollama_env_config_warning,
|
|
22193
|
-
host_runtime_signals: full.host_runtime_signals
|
|
22194
|
-
});
|
|
22195
|
-
}
|
|
22196
|
-
if ((view ?? "summary") === "full") return structuredResult(full);
|
|
22197
|
-
const running = full.running?.models ?? [];
|
|
22198
|
-
const platformHealth = full.platform_health;
|
|
22199
|
-
const compact = {
|
|
22200
|
-
status: "ok",
|
|
22201
|
-
summary: `Ollama ${String(full.version?.version ?? "unknown")}; ${running.length} resident model(s).`,
|
|
22202
|
-
endpoint: full.endpoint,
|
|
22203
|
-
ollama: { endpoint: full.endpoint, version: full.version, resident_model_count: running.length },
|
|
22204
|
-
machine: full.machine,
|
|
22205
|
-
local_conservative_config_posture: full.local_conservative_config_posture,
|
|
22206
|
-
host_runtime_signals: full.host_runtime_signals,
|
|
22207
|
-
...view === "scheduler" ? {
|
|
22208
|
-
scheduler: {
|
|
22209
|
-
admission: platformHealth?.admission ?? null,
|
|
22210
|
-
ollama_num_parallel: flatConfig?.OLLAMA_NUM_PARALLEL,
|
|
22211
|
-
ollama_max_queue: flatConfig?.OLLAMA_MAX_QUEUE,
|
|
22212
|
-
proof_level: "configured_and_snapshot_only; measure concurrent execution before claiming throughput"
|
|
22213
|
-
}
|
|
22214
|
-
} : {},
|
|
22215
|
-
evidence: { runtime: "ollama_api_version_and_ps", configuration: full.ollama_env_config_source },
|
|
22216
|
-
next: view === "scheduler" ? "Use run_task preview for a per-task advisory receipt." : 'Use models{view:"installed"} to select a local model.'
|
|
22217
|
-
};
|
|
22218
|
-
return structuredResult(compact);
|
|
22219
|
-
} catch (error2) {
|
|
22220
|
-
return errorResult(error2);
|
|
22221
|
-
}
|
|
22222
|
-
}
|
|
22223
|
-
);
|
|
22224
|
-
server.registerTool(
|
|
22225
|
-
"session",
|
|
22226
|
-
{
|
|
22227
|
-
description: "Use when: retaining model affinity. Do not use when: storing history or KV. Inputs: action, sessionId for delete. Returns: affinity handle or confirmation.",
|
|
22228
|
-
inputSchema: {
|
|
22229
|
-
action: external_exports.enum(["create", "delete"]).describe("create | release"),
|
|
22230
|
-
sessionId: external_exports.string().uuid().optional().describe("delete only"),
|
|
22231
|
-
endpoint: endpointParam
|
|
22232
|
-
},
|
|
22233
|
-
outputSchema: sessionResultSchema,
|
|
22234
|
-
annotations: { destructiveHint: false }
|
|
22235
|
-
},
|
|
22236
|
-
async ({ action, sessionId, endpoint }) => {
|
|
22237
|
-
try {
|
|
22238
|
-
if (action === "create") {
|
|
22239
|
-
if (sessionId !== void 0) return errorResult(new Error("sessionId is only valid for action: delete."));
|
|
22240
|
-
return structuredResult(JSON.parse(await createSession(endpoint)));
|
|
22241
|
-
}
|
|
22242
|
-
if (sessionId === void 0) return errorResult(new Error("action: delete requires sessionId."));
|
|
22243
|
-
await deleteSession(endpoint, sessionId);
|
|
22244
|
-
return structuredResult({ session_id: sessionId, deleted: true });
|
|
22245
|
-
} catch (error2) {
|
|
22246
|
-
return errorResult(error2);
|
|
22247
|
-
}
|
|
22248
|
-
}
|
|
22249
|
-
);
|
|
22250
|
-
server.registerTool(
|
|
22251
|
-
"models",
|
|
22252
|
-
{
|
|
22253
|
-
description: 'Use when: inspecting models. Do not use when: executing or changing state; search never permits a pull. Inputs: one view and its fields. Returns: inventory/detail, placement, or candidates. Next: library family, then model:"<family>" for tags/fit.',
|
|
22254
|
-
inputSchema: {
|
|
22255
|
-
view: external_exports.enum(["installed", "resident", "detail", "raw", "library"]).optional().describe("installed (default, needs serve) | resident (needs serve) | detail | raw | library"),
|
|
22256
|
-
model: external_exports.string().min(1).optional().describe('required for view "detail"; for "library", step 2 family name'),
|
|
22257
|
-
includeVerbose: external_exports.boolean().optional().describe('"detail" only. Adds license/modelfile \u2014 the bulk of that payload, never routing-relevant'),
|
|
22258
|
-
query: external_exports.string().min(1).optional().describe('"library" step 1: free text, e.g. "qwen", "embed"'),
|
|
22259
|
-
capabilities: external_exports.array(external_exports.enum(["vision", "tools", "thinking", "embedding", "cloud"])).min(1).max(5).optional().describe('"library" step 1: filter chips; combined as AND by the site'),
|
|
22260
|
-
order: external_exports.enum(["popular", "newest"]).optional().describe('"library" step 1. default "popular" \u2014 prefer it'),
|
|
22261
|
-
limit: external_exports.number().int().positive().max(50).optional().describe('"library" search or raw/tag page size; raw default 20'),
|
|
22262
|
-
cursor: external_exports.string().min(1).optional().describe("opaque continuation cursor for raw models or library tags"),
|
|
22263
|
-
endpoint: endpointParam,
|
|
22264
|
-
ollamaEndpoint: ollamaEndpointParam
|
|
22265
|
-
},
|
|
22266
|
-
outputSchema: modelsResultSchema,
|
|
22267
|
-
annotations: { readOnlyHint: true }
|
|
22268
|
-
},
|
|
22269
|
-
async ({ view, model, includeVerbose, query, capabilities, order, limit, cursor, endpoint, ollamaEndpoint }) => {
|
|
22270
|
-
try {
|
|
22271
|
-
const selectedView = view ?? "installed";
|
|
22272
|
-
if (selectedView !== "library" && [query, capabilities, order].some((value) => value !== void 0)) {
|
|
22273
|
-
return errorResult(new Error(`view "${selectedView}" does not accept library search fields.`));
|
|
22274
|
-
}
|
|
22275
|
-
if (selectedView !== "library" && selectedView !== "raw" && [limit, cursor].some((value) => value !== void 0)) {
|
|
22276
|
-
return errorResult(new Error(`view "${selectedView}" accepts neither pagination nor library search fields.`));
|
|
22277
|
-
}
|
|
22278
|
-
if (selectedView !== "detail" && includeVerbose !== void 0) {
|
|
22279
|
-
return errorResult(new Error('`includeVerbose` is valid only for view "detail".'));
|
|
22280
|
-
}
|
|
22281
|
-
if (selectedView !== "detail" && selectedView !== "library" && model !== void 0) {
|
|
22282
|
-
return errorResult(new Error('`model` is valid only for views "detail" and "library".'));
|
|
22283
|
-
}
|
|
22284
|
-
if (selectedView === "library" && model && [query, capabilities, order].some((value) => value !== void 0)) {
|
|
22285
|
-
return errorResult(
|
|
22286
|
-
new Error("Library step 2 accepts only `model` plus endpoint overrides; omit step-1 search fields.")
|
|
22287
|
-
);
|
|
22288
|
-
}
|
|
22289
|
-
switch (selectedView) {
|
|
22290
|
-
case "raw": {
|
|
22291
|
-
const raw = await ollamaFetch(ollamaEndpoint, "/api/tags");
|
|
22292
|
-
const models = Array.isArray(raw.models) ? raw.models : [];
|
|
22293
|
-
const page = pageLiveList(models, limit, cursor, (model2) => String(model2.name ?? JSON.stringify(model2)));
|
|
22294
|
-
return structuredResult({ ...raw, models: page.items, page: { returned: page.returned, total: page.total, next_cursor: page.next_cursor } });
|
|
22295
|
-
}
|
|
22296
|
-
case "resident": {
|
|
22297
|
-
const managed = parsedResult(await listModels(endpoint));
|
|
22298
|
-
if (!("structuredContent" in managed)) return managed;
|
|
22299
|
-
const data = managed.structuredContent;
|
|
22300
|
-
const models = (data.models ?? []).filter((entry) => entry.resident === true).map((entry) => {
|
|
22301
|
-
const size = typeof entry.size === "number" ? entry.size : null;
|
|
22302
|
-
const vram = typeof entry.resident_vram === "number" ? entry.resident_vram : null;
|
|
22303
|
-
if (size === null || vram === null || size === 0) return entry;
|
|
22304
|
-
const gpuPercent = Math.round(vram / size * 100);
|
|
22305
|
-
const execution = entry.execution;
|
|
22306
|
-
const assignedCpu = execution?.placement === "cpu";
|
|
22307
|
-
const observedProcessor = execution?.observation?.processor;
|
|
22308
|
-
const processor = observedProcessor === "cpu" || observedProcessor === "gpu" || observedProcessor === "mixed" ? observedProcessor : gpuPercent >= 100 ? "gpu" : gpuPercent <= 0 ? "cpu" : "mixed";
|
|
22309
|
-
const mismatch = execution?.observation?.status === "mismatch";
|
|
22310
|
-
return {
|
|
22311
|
-
...entry,
|
|
22312
|
-
placement: {
|
|
22313
|
-
gpu_percent: gpuPercent,
|
|
22314
|
-
assigned: assignedCpu,
|
|
22315
|
-
processor: processor === "gpu" ? "100% GPU" : processor === "cpu" ? "100% CPU" : `${gpuPercent}% GPU / ${100 - gpuPercent}% CPU`,
|
|
22316
|
-
...mismatch ? {
|
|
22317
|
-
warning: `Configured ${execution?.placement ?? "unknown"} backend disagrees with Ollama /api/ps: ${processor} was physically observed. This sample is excluded from adaptive routing feedback.`
|
|
22318
|
-
} : processor === "mixed" ? {
|
|
22319
|
-
warning: 'Partially offloaded to CPU \u2014 expect a large slowdown. Free VRAM (`ollama_manage` action "stop") or lower the context length.'
|
|
22320
|
-
} : {}
|
|
22321
|
-
}
|
|
22322
|
-
};
|
|
22323
|
-
});
|
|
22324
|
-
return structuredResult({ ...data, models });
|
|
22325
|
-
}
|
|
22326
|
-
case "detail": {
|
|
22327
|
-
if (!model) {
|
|
22328
|
-
return errorResult(new Error('view "detail" needs `model` set to an installed tag.'));
|
|
22329
|
-
}
|
|
22330
|
-
const data = await ollamaFetch(ollamaEndpoint, "/api/show", {
|
|
22331
|
-
method: "POST",
|
|
22332
|
-
body: { model }
|
|
22333
|
-
});
|
|
22334
|
-
const { license, modelfile, ...rest } = data;
|
|
22335
|
-
const modelInfo = rest.model_info ?? {};
|
|
22336
|
-
const contextEntry = Object.entries(modelInfo).find(
|
|
22337
|
-
([key, value]) => key.endsWith(".context_length") && typeof value === "number"
|
|
22338
|
-
);
|
|
22339
|
-
return structuredResult({
|
|
22340
|
-
...rest,
|
|
22341
|
-
max_context_length: contextEntry?.[1] ?? null,
|
|
22342
|
-
...includeVerbose ? { license, modelfile } : {}
|
|
22343
|
-
});
|
|
22344
|
-
}
|
|
22345
|
-
case "library":
|
|
22346
|
-
return await libraryLookup({ model, query, capabilities, order, limit, cursor, endpoint, ollamaEndpoint });
|
|
22347
|
-
default:
|
|
22348
|
-
return parsedResult(await listModels(endpoint));
|
|
22349
|
-
}
|
|
22350
|
-
} catch (error2) {
|
|
22351
|
-
return errorResult(error2);
|
|
22352
|
-
}
|
|
22353
|
-
}
|
|
22354
|
-
);
|
|
22355
|
-
async function libraryLookup({
|
|
22356
|
-
model,
|
|
22357
|
-
query,
|
|
22358
|
-
capabilities,
|
|
22359
|
-
order,
|
|
22360
|
-
limit,
|
|
22361
|
-
cursor,
|
|
22362
|
-
endpoint,
|
|
22363
|
-
ollamaEndpoint
|
|
22364
|
-
}) {
|
|
22365
|
-
const fetchPage = async (url) => {
|
|
22366
|
-
const controller = new AbortController();
|
|
22367
|
-
const timer = setTimeout(() => controller.abort(), DEFAULT_OLLAMA_FETCH_TIMEOUT_SECONDS * 1e3);
|
|
22368
|
-
try {
|
|
22369
|
-
const response = await fetch(url, { signal: controller.signal });
|
|
22370
|
-
if (!response.ok) throw new Error(`ollama.com returned HTTP ${response.status}`);
|
|
22371
|
-
return await response.text();
|
|
22372
|
-
} finally {
|
|
22373
|
-
clearTimeout(timer);
|
|
22374
|
-
}
|
|
22375
|
-
};
|
|
22376
|
-
const localState = async () => {
|
|
22377
|
-
let installed = /* @__PURE__ */ new Set();
|
|
22378
|
-
let memoryBytes = null;
|
|
22379
|
-
try {
|
|
22380
|
-
const tags = await ollamaFetch(ollamaEndpoint, "/api/tags");
|
|
22381
|
-
installed = new Set((tags.models ?? []).map((m) => m.name ?? ""));
|
|
22382
|
-
} catch {
|
|
22383
|
-
}
|
|
22384
|
-
try {
|
|
22385
|
-
const profile = JSON.parse(await machine(endpoint));
|
|
22386
|
-
memoryBytes = profile.memory_bytes ?? profile.unified_memory_bytes ?? null;
|
|
22387
|
-
} catch {
|
|
22388
|
-
}
|
|
22389
|
-
return { installed, memoryBytes };
|
|
22390
|
-
};
|
|
22391
22742
|
try {
|
|
22392
22743
|
if (model) {
|
|
22393
|
-
const
|
|
22394
|
-
|
|
22395
|
-
const {
|
|
22396
|
-
const
|
|
22397
|
-
|
|
22744
|
+
const ref = libraryReference(model);
|
|
22745
|
+
if (!ref) throw new Error("Use an exact Ollama family or namespace/model name, not a URL or path.");
|
|
22746
|
+
const { family } = ref;
|
|
22747
|
+
const library = await ollamaLibrary.lookup(model);
|
|
22748
|
+
if (library.status !== "available") return structuredResult({
|
|
22749
|
+
family,
|
|
22750
|
+
sourcePage: ref.url,
|
|
22751
|
+
tags: [],
|
|
22752
|
+
tagsUnavailable: library.message,
|
|
22753
|
+
library,
|
|
22754
|
+
recommendation: null
|
|
22755
|
+
});
|
|
22756
|
+
const tags = library.tags;
|
|
22757
|
+
const { installed: installed2, installedUnavailable: installedUnavailable2, memoryBytes } = await localState();
|
|
22758
|
+
const budget = memoryBytes ? memoryBytes * HOST_MEMORY_FIT_FRACTION : null;
|
|
22398
22759
|
const annotated = tags.map((t) => ({
|
|
22399
22760
|
...t,
|
|
22400
|
-
installed: installed2.has(t.tag),
|
|
22761
|
+
installed: installed2 ? installed2.has(t.tag) : null,
|
|
22401
22762
|
fitsInMemory: budget && t.sizeBytes ? t.sizeBytes <= budget : null,
|
|
22402
22763
|
fitScope: "host_memory_budget_only"
|
|
22403
22764
|
}));
|
|
22404
|
-
const runnable = annotated.filter((t) => t.fitsInMemory === true &&
|
|
22405
|
-
const
|
|
22406
|
-
const isEmbeddingFamily = embeddingMentions >= 3 || /embed/i.test(family);
|
|
22765
|
+
const runnable = annotated.filter((t) => t.fitsInMemory === true && !t.cloud);
|
|
22766
|
+
const isEmbeddingFamily = library.card.capabilities.includes("embedding");
|
|
22407
22767
|
const sized = runnable.filter((t) => t.sizeBytes);
|
|
22408
22768
|
const best = budget ? isEmbeddingFamily ? sized.sort((a, b) => (a.sizeBytes ?? 0) - (b.sizeBytes ?? 0))[0] : sized.sort((a, b) => (b.sizeBytes ?? 0) - (a.sizeBytes ?? 0))[0] : void 0;
|
|
22409
22769
|
const tagPage = pageLiveList(annotated, limit, cursor, (tag) => tag.tag);
|
|
22410
22770
|
return structuredResult({
|
|
22411
22771
|
family,
|
|
22412
|
-
sourcePage:
|
|
22413
|
-
|
|
22414
|
-
|
|
22415
|
-
|
|
22772
|
+
sourcePage: ref.url,
|
|
22773
|
+
library: {
|
|
22774
|
+
status: library.status,
|
|
22775
|
+
sourceUrl: library.sourceUrl,
|
|
22776
|
+
tagsSourceUrl: library.tagsSourceUrl,
|
|
22777
|
+
fetchedAt: library.fetchedAt,
|
|
22778
|
+
expiresAt: library.expiresAt,
|
|
22779
|
+
cached: library.cached,
|
|
22780
|
+
...publicModelGuidance(library, void 0, includeReadme)
|
|
22781
|
+
},
|
|
22782
|
+
...installedUnavailable2 ? { installedUnavailable: installedUnavailable2 } : {},
|
|
22416
22783
|
machineMemoryBytes: memoryBytes,
|
|
22417
22784
|
fitBudgetBytes: budget,
|
|
22418
22785
|
tags: tagPage.items,
|
|
22419
22786
|
page: { returned: tagPage.returned, total: tagPage.total, next_cursor: tagPage.next_cursor },
|
|
22420
22787
|
recommendationUnavailable: budget ? void 0 : "No machine profile (freellama serve unreachable), so memory fit could not be checked and no tag is recommended. Start serve, or read the sizes yourself.",
|
|
22421
|
-
recommendation: best ? isEmbeddingFamily
|
|
22422
|
-
tag: best.tag,
|
|
22423
|
-
why: "Smallest tag that fits \u2014 this is an embedding family, where bigger is NOT better. Measured on real retrieval over this repo: nomic-embed-text (274MB) scored 5/6 recall@3 in 4.2s, while qwen3-embedding:0.6b (2.3x the size) scored 4/6 at 3.5x the indexing cost. Embeddings do no sampling, so there is no accuracy cliff for size to buy you past.",
|
|
22424
|
-
configure: 'Batch your inputs in one `run_task` call and use keepAlive:"0" \u2014 an embedding model has no reason to hold memory after the vectors are computed. Leave returnEmbeddings off unless you are storing the vectors yourself.',
|
|
22425
|
-
caution: "Index once, query many times, and own the staleness: FreeLlama stores no vectors, and a stale index fails silently by returning confidently wrong files. Also, if you know the keyword, grep beat embedding search here on accuracy, latency and cost at the same time \u2014 reach for embeddings when there is no keyword to search for."
|
|
22426
|
-
} : {
|
|
22427
|
-
tag: best.tag,
|
|
22428
|
-
why: `Largest tag inside the conservative ~60% host-memory preflight${memoryBytes ? ` of ${Math.round(memoryBytes / 1e9)}GB` : ""}. This proves neither accelerator fit nor task quality; preview and benchmark before pulling.`,
|
|
22429
|
-
configure: `Send an explicit num_ctx rather than inheriting Ollama's default, which is 4096 tokens by default unless the Ollama service or request sets num_ctx. Use keepAlive:"0" for one-off calls so it does not hold memory after.`,
|
|
22430
|
-
caution: 'Do not co-resident large models until their combined runner sizes, contexts, KV caches, and OS headroom fit this host. Check `models{view:"resident"}` and accelerator telemetry first; benchmark packaging rather than inferring quality or speed from a model suffix.'
|
|
22431
|
-
} : null
|
|
22788
|
+
recommendation: best ? libraryTrialCandidate(best.tag, isEmbeddingFamily) : null
|
|
22432
22789
|
});
|
|
22433
22790
|
}
|
|
22434
22791
|
const params = new URLSearchParams();
|
|
22435
22792
|
for (const c of capabilities ?? []) params.append("c", c);
|
|
22436
22793
|
if (query) params.set("q", query);
|
|
22437
22794
|
if (order === "newest") params.set("o", "newest");
|
|
22438
|
-
const
|
|
22439
|
-
const
|
|
22440
|
-
const
|
|
22441
|
-
const
|
|
22795
|
+
const search = await ollamaLibrary.search(params);
|
|
22796
|
+
const url = search.url;
|
|
22797
|
+
const parsed = search.models.slice(0, limit ?? 10);
|
|
22798
|
+
const { installed, installedUnavailable } = await localState();
|
|
22799
|
+
const installedFamilies = installed ? new Set([...installed].map((n) => n.split(":")[0])) : null;
|
|
22442
22800
|
return structuredResult({
|
|
22443
22801
|
query: url,
|
|
22802
|
+
fetchedAt: search.fetchedAt,
|
|
22803
|
+
...parsed.length === 0 ? { searchUnavailable: "No result cards were recognized. The query may have no matches or Ollama's markup may have changed; inspect the source URL." } : {},
|
|
22444
22804
|
order: order ?? "popular",
|
|
22445
22805
|
count: parsed.length,
|
|
22806
|
+
...installedUnavailable ? { installedUnavailable } : {},
|
|
22446
22807
|
nextStep: 'Not pullable yet. Call again with view:"library", model:"<name>" to get tags, sizes and memory fit.',
|
|
22447
|
-
models: parsed.map((m) => ({ ...m, installed: installedFamilies.has(m.name) }))
|
|
22808
|
+
models: parsed.map((m) => ({ ...m, installed: installedFamilies ? installedFamilies.has(m.name) : null }))
|
|
22448
22809
|
});
|
|
22449
22810
|
} catch (error2) {
|
|
22450
22811
|
return errorResult(error2);
|
|
22451
22812
|
}
|
|
22452
22813
|
}
|
|
22453
|
-
|
|
22454
|
-
|
|
22455
|
-
|
|
22456
|
-
|
|
22457
|
-
|
|
22458
|
-
|
|
22459
|
-
|
|
22460
|
-
|
|
22461
|
-
|
|
22462
|
-
|
|
22463
|
-
|
|
22464
|
-
|
|
22465
|
-
|
|
22466
|
-
|
|
22467
|
-
|
|
22468
|
-
|
|
22469
|
-
|
|
22470
|
-
|
|
22471
|
-
).
|
|
22472
|
-
|
|
22473
|
-
|
|
22474
|
-
input: external_exports.union([external_exports.string().min(1), external_exports.array(external_exports.string().min(1)).min(1)]).optional().describe("embedding only; batch strings because one call is far cheaper than one call per item"),
|
|
22475
|
-
tools: external_exports.array(external_exports.record(external_exports.unknown())).min(1).optional().describe("Ollama function definitions for chat tasks."),
|
|
22476
|
-
keepAlive: external_exports.string().min(1).optional().describe('"0" unloads now, "-1" pins, default 5m'),
|
|
22477
|
-
format: external_exports.union([external_exports.literal("json"), external_exports.record(external_exports.unknown())]).optional().describe('Ollama structured output: "json" or a JSON schema object'),
|
|
22478
|
-
think: external_exports.union([external_exports.boolean(), external_exports.enum(["low", "medium", "high"])]).optional().describe("Override the task profile for thinking-capable models"),
|
|
22479
|
-
options: external_exports.record(external_exports.unknown()).optional().describe("Advanced Ollama options; num_ctx/contextTokens and num_gpu/placement are routing-owned. num_predict caps output."),
|
|
22480
|
-
logprobs: external_exports.boolean().optional(),
|
|
22481
|
-
topLogprobs: external_exports.number().int().nonnegative().optional().describe("Requires logprobs:true"),
|
|
22482
|
-
minConfidence: minConfidenceParam,
|
|
22483
|
-
priority: external_exports.enum(["interactive", "normal", "background"]).optional().describe("Admission class only; normal default. Fair scheduling prevents background starvation."),
|
|
22484
|
-
returnEmbeddings: external_exports.boolean().optional().describe("false (default) withholds the raw vectors; they are large and unreadable to a model"),
|
|
22485
|
-
preview: external_exports.boolean().optional().describe(
|
|
22486
|
-
"true = routing fields only; rejects payloads and runtime controls; never executes"
|
|
22487
|
-
)
|
|
22814
|
+
function registerModelsTool(server2) {
|
|
22815
|
+
server2.registerTool(
|
|
22816
|
+
"models",
|
|
22817
|
+
{
|
|
22818
|
+
description: "Use when: listing installed or loaded models with capabilities, policy rank and memory fit, inspecting one model, or searching ollama.com (view:library). Do not use when: running work (run_task) or pulling (ollama_manage). Returns: models; library lists families first, then one family's tags.",
|
|
22819
|
+
inputSchema: external_exports.object({
|
|
22820
|
+
view: external_exports.enum(["installed", "resident", "detail", "raw", "library"]).optional(),
|
|
22821
|
+
model: external_exports.string().min(1).optional(),
|
|
22822
|
+
includeVerbose: external_exports.boolean().optional(),
|
|
22823
|
+
includeLibrary: external_exports.boolean().optional(),
|
|
22824
|
+
includeReadme: external_exports.boolean().optional(),
|
|
22825
|
+
query: external_exports.string().min(1).optional(),
|
|
22826
|
+
capabilities: external_exports.array(external_exports.enum(["vision", "tools", "thinking", "embedding", "cloud"])).min(1).max(5).optional(),
|
|
22827
|
+
order: external_exports.enum(["popular", "newest"]).optional(),
|
|
22828
|
+
limit: external_exports.number().int().positive().max(50).optional(),
|
|
22829
|
+
cursor: external_exports.string().min(1).optional(),
|
|
22830
|
+
endpoint: endpointParam,
|
|
22831
|
+
ollamaEndpoint: ollamaEndpointParam
|
|
22832
|
+
}).strict(),
|
|
22833
|
+
outputSchema: modelsResultSchema,
|
|
22834
|
+
annotations: { readOnlyHint: true }
|
|
22488
22835
|
},
|
|
22489
|
-
|
|
22490
|
-
|
|
22491
|
-
|
|
22492
|
-
|
|
22493
|
-
|
|
22494
|
-
task,
|
|
22495
|
-
objective,
|
|
22496
|
-
model,
|
|
22497
|
-
sessionId,
|
|
22498
|
-
contextTokens,
|
|
22499
|
-
executionPreference,
|
|
22500
|
-
minPlacementEvidence,
|
|
22501
|
-
requiredCapabilities,
|
|
22502
|
-
prompt,
|
|
22503
|
-
images,
|
|
22504
|
-
messages,
|
|
22505
|
-
input,
|
|
22506
|
-
tools,
|
|
22507
|
-
keepAlive,
|
|
22508
|
-
format,
|
|
22509
|
-
think,
|
|
22510
|
-
options,
|
|
22511
|
-
logprobs,
|
|
22512
|
-
topLogprobs,
|
|
22513
|
-
minConfidence,
|
|
22514
|
-
priority,
|
|
22515
|
-
returnEmbeddings,
|
|
22516
|
-
preview
|
|
22517
|
-
}) => {
|
|
22518
|
-
try {
|
|
22519
|
-
const canonicalTask = canonicalTaskKind(task);
|
|
22520
|
-
if (preview) {
|
|
22521
|
-
const executionOnlyFields = [
|
|
22522
|
-
["prompt", prompt],
|
|
22523
|
-
["images", images],
|
|
22524
|
-
["messages", messages],
|
|
22525
|
-
["input", input],
|
|
22526
|
-
["tools", tools],
|
|
22527
|
-
["keepAlive", keepAlive],
|
|
22528
|
-
["format", format],
|
|
22529
|
-
["think", think],
|
|
22530
|
-
["options", options],
|
|
22531
|
-
["logprobs", logprobs],
|
|
22532
|
-
["topLogprobs", topLogprobs],
|
|
22533
|
-
["returnEmbeddings", returnEmbeddings]
|
|
22534
|
-
].filter(([, value]) => value !== void 0).map(([name]) => `\`${name}\``);
|
|
22535
|
-
if (executionOnlyFields.length > 0) {
|
|
22536
|
-
return errorResult(
|
|
22537
|
-
new Error(
|
|
22538
|
-
`\`preview:true\` accepts routing fields only; remove execution-only fields ${executionOnlyFields.join(", ")}. Use \`requiredCapabilities:["tools"]\` to preview tool capability, then make a separate execution call with the payload.`
|
|
22539
|
-
)
|
|
22540
|
-
);
|
|
22836
|
+
async ({ view, model, includeVerbose, includeLibrary, includeReadme, query, capabilities, order, limit, cursor, endpoint, ollamaEndpoint }) => {
|
|
22837
|
+
try {
|
|
22838
|
+
const selectedView = view ?? "installed";
|
|
22839
|
+
if (includeLibrary !== void 0 && selectedView !== "installed" && selectedView !== "detail") {
|
|
22840
|
+
return errorResult(new Error('`includeLibrary` is valid only for views "installed" and "detail".'));
|
|
22541
22841
|
}
|
|
22542
|
-
|
|
22543
|
-
|
|
22544
|
-
if (topLogprobs !== void 0 && logprobs !== true) {
|
|
22545
|
-
return errorResult(new Error("`topLogprobs` requires `logprobs:true`."));
|
|
22546
|
-
}
|
|
22547
|
-
if (!preview && task === "embedding") {
|
|
22548
|
-
if (input === void 0) return errorResult(new Error('task "embedding" requires `input`.'));
|
|
22549
|
-
if ([prompt, images, messages, tools, format, think, logprobs, topLogprobs].some(
|
|
22550
|
-
(value) => value !== void 0
|
|
22551
|
-
)) {
|
|
22552
|
-
return errorResult(
|
|
22553
|
-
new Error('task "embedding" accepts `input`, `options`, and routing fields; chat controls are not valid.')
|
|
22554
|
-
);
|
|
22842
|
+
if (includeReadme !== void 0 && !(selectedView === "library" && model || selectedView === "detail" && includeLibrary === true)) {
|
|
22843
|
+
return errorResult(new Error('`includeReadme` needs library step 2, or view "detail" with `includeLibrary:true`.'));
|
|
22555
22844
|
}
|
|
22556
|
-
|
|
22557
|
-
|
|
22558
|
-
if (input !== void 0 || returnEmbeddings !== void 0) {
|
|
22559
|
-
return errorResult(new Error('`input` and `returnEmbeddings` are valid only for task "embedding".'));
|
|
22845
|
+
if (selectedView !== "library" && [query, capabilities, order].some((value) => value !== void 0)) {
|
|
22846
|
+
return errorResult(new Error(`view "${selectedView}" does not accept library search fields.`));
|
|
22560
22847
|
}
|
|
22561
|
-
if (
|
|
22562
|
-
return errorResult(new Error(`
|
|
22848
|
+
if (selectedView !== "library" && selectedView !== "raw" && [limit, cursor].some((value) => value !== void 0)) {
|
|
22849
|
+
return errorResult(new Error(`view "${selectedView}" accepts neither pagination nor library search fields.`));
|
|
22563
22850
|
}
|
|
22564
|
-
if (
|
|
22565
|
-
return errorResult(
|
|
22566
|
-
new Error("Top-level `images` is valid only with `prompt`; put images inside messages instead.")
|
|
22567
|
-
);
|
|
22851
|
+
if (selectedView !== "detail" && includeVerbose !== void 0) {
|
|
22852
|
+
return errorResult(new Error('`includeVerbose` is valid only for view "detail".'));
|
|
22568
22853
|
}
|
|
22569
|
-
if (
|
|
22570
|
-
return errorResult(new Error(
|
|
22854
|
+
if (selectedView !== "detail" && selectedView !== "library" && model !== void 0) {
|
|
22855
|
+
return errorResult(new Error('`model` is valid only for views "detail" and "library".'));
|
|
22571
22856
|
}
|
|
22572
|
-
|
|
22573
|
-
|
|
22574
|
-
|
|
22575
|
-
|
|
22576
|
-
);
|
|
22577
|
-
if ("structuredContent" in result2) {
|
|
22578
|
-
const refusal = belowConfidence(result2.structuredContent, minConfidence);
|
|
22579
|
-
if (refusal) return refusal;
|
|
22580
|
-
}
|
|
22581
|
-
return result2;
|
|
22582
|
-
}
|
|
22583
|
-
if (minConfidence) {
|
|
22584
|
-
const decision = parsedResult(
|
|
22585
|
-
// minConfidence is forwarded so the CORE gate refuses, with its actionable message naming
|
|
22586
|
-
// the two commands that raise the grade. The belowConfidence() check below stays only as a
|
|
22587
|
-
// fallback for servers older than the core gate.
|
|
22588
|
-
await route(endpoint, canonicalTask, objective, model, sessionId, contextTokens, effectiveRequiredCapabilities, minConfidence, executionPreference, minPlacementEvidence)
|
|
22589
|
-
);
|
|
22590
|
-
if ("structuredContent" in decision) {
|
|
22591
|
-
const refusal = belowConfidence(decision.structuredContent, minConfidence);
|
|
22592
|
-
if (refusal) return refusal;
|
|
22857
|
+
if (selectedView === "library" && model && [query, capabilities, order].some((value) => value !== void 0)) {
|
|
22858
|
+
return errorResult(
|
|
22859
|
+
new Error("Library step 2 accepts only `model` plus endpoint overrides; omit step-1 search fields.")
|
|
22860
|
+
);
|
|
22593
22861
|
}
|
|
22594
|
-
|
|
22595
|
-
|
|
22596
|
-
|
|
22597
|
-
|
|
22598
|
-
|
|
22599
|
-
|
|
22600
|
-
session_id: sessionId,
|
|
22601
|
-
context_tokens: contextTokens,
|
|
22602
|
-
required_capabilities: effectiveRequiredCapabilities ?? [],
|
|
22603
|
-
prompt,
|
|
22604
|
-
images,
|
|
22605
|
-
messages: messages ?? [],
|
|
22606
|
-
input,
|
|
22607
|
-
tools,
|
|
22608
|
-
keep_alive: keepAlive,
|
|
22609
|
-
min_confidence: minConfidence,
|
|
22610
|
-
priority: priority ?? "normal",
|
|
22611
|
-
execution_preference: executionPreference ?? "auto",
|
|
22612
|
-
min_placement_evidence: minPlacementEvidence ?? "configured",
|
|
22613
|
-
request_options: {
|
|
22614
|
-
format,
|
|
22615
|
-
think,
|
|
22616
|
-
options,
|
|
22617
|
-
logprobs,
|
|
22618
|
-
top_logprobs: topLogprobs
|
|
22862
|
+
switch (selectedView) {
|
|
22863
|
+
case "raw": {
|
|
22864
|
+
const raw = await ollamaFetch(ollamaEndpoint, "/api/tags");
|
|
22865
|
+
const models = Array.isArray(raw.models) ? raw.models : [];
|
|
22866
|
+
const page = pageLiveList(models, limit, cursor, (model2) => String(model2.name ?? JSON.stringify(model2)));
|
|
22867
|
+
return structuredResult({ ...raw, models: page.items, page: { returned: page.returned, total: page.total, next_cursor: page.next_cursor } });
|
|
22619
22868
|
}
|
|
22620
|
-
|
|
22621
|
-
|
|
22622
|
-
|
|
22623
|
-
|
|
22624
|
-
|
|
22625
|
-
|
|
22626
|
-
|
|
22627
|
-
|
|
22628
|
-
|
|
22629
|
-
|
|
22630
|
-
|
|
22631
|
-
|
|
22632
|
-
|
|
22633
|
-
|
|
22634
|
-
|
|
22635
|
-
|
|
22636
|
-
|
|
22637
|
-
|
|
22638
|
-
maxParallelism: external_exports.number().int().positive().max(64).optional(),
|
|
22639
|
-
endpoint: endpointParam
|
|
22640
|
-
},
|
|
22641
|
-
outputSchema: batchResultSchema,
|
|
22642
|
-
annotations: { destructiveHint: false }
|
|
22643
|
-
},
|
|
22644
|
-
async ({ tasks, maxParallelism, endpoint }) => {
|
|
22645
|
-
try {
|
|
22646
|
-
for (const item of tasks) {
|
|
22647
|
-
const task = item.task;
|
|
22648
|
-
if (task.task === "embedding") {
|
|
22649
|
-
if (task.input === void 0 || task.prompt !== void 0 || task.messages !== void 0 || task.tools !== void 0) {
|
|
22650
|
-
return errorResult(new Error(`batch item ${item.id}: embedding requires input and accepts no chat payload.`));
|
|
22869
|
+
case "resident": {
|
|
22870
|
+
const managed = parsedResult(await listModels2(endpoint));
|
|
22871
|
+
if (!isStructuredSuccess(managed)) return managed;
|
|
22872
|
+
const data = managed.structuredContent;
|
|
22873
|
+
const models = (data.models ?? []).filter((entry) => entry.resident === true).map((entry) => {
|
|
22874
|
+
const { resident_view: view2, ...rest } = entry;
|
|
22875
|
+
if (!view2) return rest;
|
|
22876
|
+
return {
|
|
22877
|
+
...rest,
|
|
22878
|
+
placement: {
|
|
22879
|
+
gpu_percent: view2.gpu_percent,
|
|
22880
|
+
assigned: view2.assigned,
|
|
22881
|
+
processor: view2.processor_label,
|
|
22882
|
+
...view2.warning !== void 0 ? { warning: view2.warning } : {}
|
|
22883
|
+
}
|
|
22884
|
+
};
|
|
22885
|
+
});
|
|
22886
|
+
return structuredResult({ ...data, models });
|
|
22651
22887
|
}
|
|
22652
|
-
|
|
22653
|
-
|
|
22654
|
-
|
|
22655
|
-
|
|
22656
|
-
|
|
22657
|
-
|
|
22658
|
-
|
|
22659
|
-
|
|
22660
|
-
|
|
22661
|
-
|
|
22662
|
-
|
|
22663
|
-
|
|
22664
|
-
|
|
22665
|
-
|
|
22666
|
-
|
|
22667
|
-
|
|
22668
|
-
model: task.model,
|
|
22669
|
-
session_id: task.sessionId,
|
|
22670
|
-
context_tokens: task.contextTokens,
|
|
22671
|
-
execution_preference: task.executionPreference ?? "auto",
|
|
22672
|
-
min_placement_evidence: task.minPlacementEvidence ?? "configured",
|
|
22673
|
-
required_capabilities: task.requiredCapabilities ?? [],
|
|
22674
|
-
priority: task.priority ?? "normal",
|
|
22675
|
-
prompt: task.prompt,
|
|
22676
|
-
images: task.images,
|
|
22677
|
-
messages: task.messages ?? [],
|
|
22678
|
-
input: task.input,
|
|
22679
|
-
tools: task.tools,
|
|
22680
|
-
keep_alive: task.keepAlive,
|
|
22681
|
-
min_confidence: task.minConfidence,
|
|
22682
|
-
request_options: {
|
|
22683
|
-
format: task.format,
|
|
22684
|
-
think: task.think,
|
|
22685
|
-
options: task.options,
|
|
22686
|
-
logprobs: task.logprobs,
|
|
22687
|
-
top_logprobs: task.topLogprobs
|
|
22888
|
+
case "detail": {
|
|
22889
|
+
if (!model) {
|
|
22890
|
+
return errorResult(new Error('view "detail" needs `model` set to an installed tag.'));
|
|
22891
|
+
}
|
|
22892
|
+
const data = await ollamaFetch(ollamaEndpoint, "/api/show", {
|
|
22893
|
+
method: "POST",
|
|
22894
|
+
body: { model }
|
|
22895
|
+
});
|
|
22896
|
+
let local = data;
|
|
22897
|
+
if (includeLibrary && !isRemoteModel(data, model)) {
|
|
22898
|
+
try {
|
|
22899
|
+
const inventory = await ollamaFetch(ollamaEndpoint, "/api/tags");
|
|
22900
|
+
const canonical = libraryReference(model)?.tag;
|
|
22901
|
+
const entry = canonical && inventory.models?.find((entry2) => libraryReference(String(entry2.name ?? entry2.model))?.tag === canonical);
|
|
22902
|
+
if (entry) local = { ...entry, ...data, digest: entry.digest };
|
|
22903
|
+
} catch {
|
|
22688
22904
|
}
|
|
22689
22905
|
}
|
|
22690
|
-
|
|
22691
|
-
|
|
22692
|
-
|
|
22693
|
-
|
|
22694
|
-
|
|
22695
|
-
|
|
22696
|
-
|
|
22697
|
-
|
|
22698
|
-
|
|
22699
|
-
|
|
22700
|
-
|
|
22701
|
-
|
|
22702
|
-
|
|
22703
|
-
|
|
22704
|
-
|
|
22705
|
-
|
|
22706
|
-
|
|
22707
|
-
|
|
22708
|
-
|
|
22709
|
-
|
|
22710
|
-
|
|
22711
|
-
|
|
22712
|
-
|
|
22713
|
-
|
|
22714
|
-
|
|
22715
|
-
|
|
22716
|
-
|
|
22717
|
-
|
|
22718
|
-
|
|
22719
|
-
body: { model, keep_alive: 0 }
|
|
22720
|
-
});
|
|
22721
|
-
const payload = data;
|
|
22722
|
-
if (typeof payload.error === "string" && payload.error) {
|
|
22723
|
-
return errorResult(new Error(`${action} ${model} failed: ${payload.error}`));
|
|
22906
|
+
const library = includeLibrary && !isRemoteModel(local, model) ? await ollamaLibrary.lookup(model) : void 0;
|
|
22907
|
+
const { license, modelfile, ...rest } = data;
|
|
22908
|
+
const modelInfo = rest.model_info ?? {};
|
|
22909
|
+
const contextEntry = Object.entries(modelInfo).find(
|
|
22910
|
+
([key, value]) => key.endsWith(".context_length") && typeof value === "number"
|
|
22911
|
+
);
|
|
22912
|
+
return structuredResult({
|
|
22913
|
+
...rest,
|
|
22914
|
+
max_context_length: contextEntry?.[1] ?? null,
|
|
22915
|
+
knowledge: modelKnowledge(model, local, library, includeReadme),
|
|
22916
|
+
...includeVerbose ? { license, modelfile } : {}
|
|
22917
|
+
});
|
|
22918
|
+
}
|
|
22919
|
+
case "library":
|
|
22920
|
+
return await libraryLookup({ model, query, capabilities, order, limit, cursor, includeReadme, endpoint, ollamaEndpoint });
|
|
22921
|
+
default: {
|
|
22922
|
+
const result = parsedResult(await listModels2(endpoint));
|
|
22923
|
+
if (!isStructuredSuccess(result)) return result;
|
|
22924
|
+
const data = result.structuredContent;
|
|
22925
|
+
return structuredResult({ ...data, models: await enrichInstalledModels(
|
|
22926
|
+
data.models ?? [],
|
|
22927
|
+
ollamaLibrary,
|
|
22928
|
+
includeLibrary ?? false,
|
|
22929
|
+
async (upstream) => (await ollamaFetch(upstream ?? ollamaEndpoint, "/api/tags")).models ?? []
|
|
22930
|
+
) });
|
|
22931
|
+
}
|
|
22932
|
+
}
|
|
22933
|
+
} catch (error2) {
|
|
22934
|
+
return errorResult(error2);
|
|
22724
22935
|
}
|
|
22725
|
-
return structuredResult(payload);
|
|
22726
|
-
} catch (error2) {
|
|
22727
|
-
return errorResult(error2);
|
|
22728
|
-
}
|
|
22729
|
-
}
|
|
22730
|
-
);
|
|
22731
|
-
server.registerTool(
|
|
22732
|
-
"ollama_delete",
|
|
22733
|
-
{
|
|
22734
|
-
description: "DESTRUCTIVE AND IRREVERSIBLE. Use when: a human requests one exact tag. Do not use when: freeing memory, cleaning by age, or inferring a tag. Inputs: exact tag. Returns: deleted tag. Next: refresh models.",
|
|
22735
|
-
inputSchema: {
|
|
22736
|
-
model: external_exports.string().min(1),
|
|
22737
|
-
ollamaEndpoint: ollamaEndpointParam
|
|
22738
|
-
},
|
|
22739
|
-
outputSchema: deleteResultSchema,
|
|
22740
|
-
annotations: { destructiveHint: true }
|
|
22741
|
-
},
|
|
22742
|
-
async ({ model, ollamaEndpoint }) => {
|
|
22743
|
-
try {
|
|
22744
|
-
await ollamaFetch(ollamaEndpoint, "/api/delete", {
|
|
22745
|
-
method: "DELETE",
|
|
22746
|
-
body: { name: model }
|
|
22747
|
-
});
|
|
22748
|
-
return structuredResult({ deleted: model });
|
|
22749
|
-
} catch (error2) {
|
|
22750
|
-
return errorResult(error2);
|
|
22751
22936
|
}
|
|
22752
|
-
|
|
22753
|
-
|
|
22754
|
-
|
|
22755
|
-
|
|
22756
|
-
|
|
22757
|
-
|
|
22758
|
-
|
|
22759
|
-
|
|
22760
|
-
|
|
22761
|
-
adapter: external_exports.enum(["bash", "octocode"]).optional().describe('"bash" (default) beat "octocode" on every model measured, and is faster'),
|
|
22762
|
-
model: external_exports.string().min(1).optional().describe("omit to use FREELLAMA_MCP_DEFAULT_MODEL; research accuracy collapses below ~12B"),
|
|
22937
|
+
);
|
|
22938
|
+
}
|
|
22939
|
+
|
|
22940
|
+
// src/tools/scope.ts
|
|
22941
|
+
function registerScopeTool(server2) {
|
|
22942
|
+
server2.registerTool("scope", {
|
|
22943
|
+
description: "Use when: a multi-turn local conversation should keep its message history server-side (create/get/fork/delete), then continue with run_task {scopeId, scopeRevision}. Do not use when: you want model affinity (session) or runner KV control. Returns: process-local scope metadata; message history only with includeMessages:true.",
|
|
22944
|
+
inputSchema: external_exports.object({
|
|
22945
|
+
action: external_exports.enum(["create", "get", "fork", "delete"]),
|
|
22763
22946
|
endpoint: endpointParam,
|
|
22764
|
-
|
|
22765
|
-
|
|
22766
|
-
|
|
22767
|
-
|
|
22768
|
-
|
|
22769
|
-
|
|
22770
|
-
|
|
22771
|
-
|
|
22772
|
-
seed: external_exports.number().int().nonnegative().optional(),
|
|
22773
|
-
think: external_exports.boolean().optional(),
|
|
22774
|
-
keepAlive: external_exports.string().min(1).optional().describe('Ollama duration such as "5m", "0", or "-1"'),
|
|
22775
|
-
requestTimeoutSeconds: external_exports.number().positive().optional(),
|
|
22776
|
-
toolTimeoutSeconds: external_exports.number().positive().optional(),
|
|
22777
|
-
retryAttempts: external_exports.number().int().positive().optional(),
|
|
22778
|
-
retryBackoffSeconds: external_exports.number().nonnegative().optional(),
|
|
22779
|
-
maxParseRepairs: external_exports.number().int().nonnegative().optional(),
|
|
22780
|
-
parseRepairEchoChars: external_exports.number().int().positive().optional(),
|
|
22781
|
-
context: external_exports.object({
|
|
22782
|
-
charsPerToken: external_exports.number().positive().optional(),
|
|
22783
|
-
safetyMarginTokens: external_exports.number().int().nonnegative().optional(),
|
|
22784
|
-
imageTokenEstimate: external_exports.number().int().nonnegative().optional(),
|
|
22785
|
-
keepRecent: external_exports.number().int().nonnegative().optional(),
|
|
22786
|
-
compactPreviewChars: external_exports.number().int().positive().optional(),
|
|
22787
|
-
compactRetainRatio: external_exports.number().positive().lt(1).optional(),
|
|
22788
|
-
clipHeadRatio: external_exports.number().positive().lt(1).optional(),
|
|
22789
|
-
observationPageChars: external_exports.number().int().positive().optional(),
|
|
22790
|
-
pinnedOverflow: external_exports.enum(["error", "clip"]).optional()
|
|
22791
|
-
}).optional()
|
|
22792
|
-
}).optional().describe("Per-call agent budget, recovery, and compaction controls.")
|
|
22793
|
-
},
|
|
22794
|
-
outputSchema: researchResultSchema,
|
|
22795
|
-
annotations: { destructiveHint: false }
|
|
22796
|
-
},
|
|
22797
|
-
async ({ question, workspacePath, adapter, model, endpoint, executionPreference, minPlacementEvidence, legacyText, agent }) => {
|
|
22798
|
-
const chosenAdapter = adapter ?? DEFAULT_RESEARCH_ADAPTER;
|
|
22799
|
-
const chosenModel = model ?? DEFAULT_DELEGATE_MODEL;
|
|
22800
|
-
let resolvedWorkspace;
|
|
22947
|
+
scopeId: external_exports.string().uuid().optional(),
|
|
22948
|
+
revision: external_exports.number().int().nonnegative().optional(),
|
|
22949
|
+
messages: messagesParam.unwrap().min(0).optional(),
|
|
22950
|
+
includeMessages: external_exports.boolean().optional()
|
|
22951
|
+
}).strict(),
|
|
22952
|
+
outputSchema: objectResultSchema,
|
|
22953
|
+
annotations: { destructiveHint: true }
|
|
22954
|
+
}, async ({ action, endpoint, scopeId, revision, messages, includeMessages }) => {
|
|
22801
22955
|
try {
|
|
22802
|
-
|
|
22956
|
+
if (action === "create" !== (scopeId === void 0)) throw new Error("scopeId is required for get/fork/delete, forbidden for create.");
|
|
22957
|
+
if (action === "fork" !== (revision !== void 0)) throw new Error("revision is required only for fork.");
|
|
22958
|
+
if (includeMessages !== void 0 && action !== "get") throw new Error("includeMessages is valid only for get.");
|
|
22959
|
+
if (messages !== void 0 && action !== "create") throw new Error("messages is valid only for create.");
|
|
22960
|
+
if (action === "delete") {
|
|
22961
|
+
await deleteScope2(endpoint, scopeId);
|
|
22962
|
+
return structuredResult({ deleted: true, scope_id: scopeId });
|
|
22963
|
+
}
|
|
22964
|
+
return parsedResult(action === "create" ? await createScope2(endpoint, { messages }) : action === "fork" ? await forkScope2(endpoint, scopeId, { revision }) : await getScope2(endpoint, scopeId, includeMessages));
|
|
22803
22965
|
} catch (error2) {
|
|
22804
22966
|
return errorResult(error2);
|
|
22805
22967
|
}
|
|
22806
|
-
|
|
22807
|
-
|
|
22808
|
-
|
|
22809
|
-
|
|
22810
|
-
|
|
22811
|
-
|
|
22812
|
-
|
|
22813
|
-
|
|
22814
|
-
|
|
22815
|
-
|
|
22816
|
-
|
|
22817
|
-
|
|
22968
|
+
});
|
|
22969
|
+
}
|
|
22970
|
+
|
|
22971
|
+
// src/telemetry.ts
|
|
22972
|
+
var EXTERNAL_COST = configuredExternalCost();
|
|
22973
|
+
function withTaskTelemetry(result) {
|
|
22974
|
+
if (!isStructuredSuccess(result)) return result;
|
|
22975
|
+
const payload = result.structuredContent;
|
|
22976
|
+
const metrics = payload.metrics;
|
|
22977
|
+
return structuredResult({
|
|
22978
|
+
...payload,
|
|
22979
|
+
telemetry: costTelemetry({
|
|
22980
|
+
inputTokens: typeof metrics?.prompt_tokens === "number" ? metrics.prompt_tokens : null,
|
|
22981
|
+
outputTokens: typeof metrics?.output_tokens === "number" ? metrics.output_tokens : null,
|
|
22982
|
+
totalDurationNs: typeof metrics?.total_duration_ns === "number" ? metrics.total_duration_ns : null
|
|
22983
|
+
}, EXTERNAL_COST)
|
|
22984
|
+
}, { text: taskAnswerText(payload) });
|
|
22985
|
+
}
|
|
22986
|
+
function withBatchTelemetry(result) {
|
|
22987
|
+
if (!isStructuredSuccess(result)) return result;
|
|
22988
|
+
const payload = result.structuredContent;
|
|
22989
|
+
const rows = Array.isArray(payload.results) ? payload.results : [];
|
|
22990
|
+
let inputTokens = 0;
|
|
22991
|
+
let outputTokens = 0;
|
|
22992
|
+
let complete = 0;
|
|
22993
|
+
const results = rows.map((row) => {
|
|
22994
|
+
if (!row || typeof row !== "object") return row;
|
|
22995
|
+
const item = row;
|
|
22996
|
+
if (item.ok !== true || !item.response || typeof item.response !== "object") return item;
|
|
22997
|
+
const response = item.response;
|
|
22998
|
+
const metrics = response.metrics;
|
|
22999
|
+
const input = typeof metrics?.prompt_tokens === "number" ? metrics.prompt_tokens : null;
|
|
23000
|
+
const output = typeof metrics?.output_tokens === "number" ? metrics.output_tokens : null;
|
|
23001
|
+
if (input !== null && output !== null) {
|
|
23002
|
+
inputTokens += input;
|
|
23003
|
+
outputTokens += output;
|
|
23004
|
+
complete += 1;
|
|
22818
23005
|
}
|
|
22819
|
-
|
|
22820
|
-
|
|
22821
|
-
|
|
22822
|
-
|
|
22823
|
-
|
|
22824
|
-
|
|
22825
|
-
|
|
22826
|
-
|
|
22827
|
-
|
|
22828
|
-
|
|
22829
|
-
|
|
22830
|
-
|
|
22831
|
-
|
|
22832
|
-
|
|
22833
|
-
|
|
22834
|
-
|
|
22835
|
-
|
|
22836
|
-
|
|
22837
|
-
|
|
22838
|
-
|
|
22839
|
-
|
|
22840
|
-
|
|
22841
|
-
|
|
22842
|
-
|
|
22843
|
-
|
|
22844
|
-
|
|
22845
|
-
|
|
22846
|
-
|
|
22847
|
-
|
|
22848
|
-
|
|
22849
|
-
|
|
22850
|
-
|
|
22851
|
-
|
|
22852
|
-
|
|
22853
|
-
|
|
22854
|
-
|
|
22855
|
-
|
|
22856
|
-
|
|
22857
|
-
|
|
22858
|
-
|
|
22859
|
-
|
|
22860
|
-
|
|
22861
|
-
|
|
22862
|
-
|
|
22863
|
-
|
|
22864
|
-
|
|
22865
|
-
|
|
22866
|
-
|
|
22867
|
-
|
|
22868
|
-
|
|
22869
|
-
|
|
22870
|
-
|
|
22871
|
-
|
|
22872
|
-
|
|
22873
|
-
|
|
22874
|
-
|
|
22875
|
-
|
|
22876
|
-
|
|
22877
|
-
|
|
22878
|
-
|
|
22879
|
-
|
|
22880
|
-
|
|
22881
|
-
|
|
22882
|
-
|
|
22883
|
-
|
|
22884
|
-
|
|
22885
|
-
|
|
22886
|
-
|
|
22887
|
-
|
|
22888
|
-
|
|
22889
|
-
|
|
22890
|
-
|
|
22891
|
-
|
|
22892
|
-
|
|
22893
|
-
|
|
22894
|
-
|
|
22895
|
-
|
|
22896
|
-
|
|
22897
|
-
|
|
22898
|
-
|
|
22899
|
-
|
|
22900
|
-
|
|
22901
|
-
|
|
22902
|
-
|
|
22903
|
-
|
|
22904
|
-
|
|
22905
|
-
|
|
22906
|
-
|
|
22907
|
-
|
|
22908
|
-
|
|
22909
|
-
|
|
22910
|
-
|
|
22911
|
-
|
|
22912
|
-
|
|
22913
|
-
|
|
22914
|
-
|
|
22915
|
-
|
|
22916
|
-
|
|
22917
|
-
|
|
22918
|
-
|
|
22919
|
-
|
|
22920
|
-
|
|
22921
|
-
|
|
22922
|
-
|
|
22923
|
-
|
|
22924
|
-
|
|
22925
|
-
|
|
22926
|
-
|
|
22927
|
-
|
|
22928
|
-
|
|
22929
|
-
|
|
22930
|
-
|
|
22931
|
-
|
|
23006
|
+
return { ...item, response: { ...response, telemetry: costTelemetry({ inputTokens: input, outputTokens: output }, EXTERNAL_COST) } };
|
|
23007
|
+
});
|
|
23008
|
+
return structuredResult({
|
|
23009
|
+
...payload,
|
|
23010
|
+
results,
|
|
23011
|
+
telemetry: complete === rows.filter((row) => row?.ok === true).length ? costTelemetry({ inputTokens, outputTokens }, EXTERNAL_COST) : { local: null, externalEquivalent: null, note: "Batch aggregate unavailable because one or more successful items omitted token counts." }
|
|
23012
|
+
}, { text: batchAnswerText(payload) });
|
|
23013
|
+
}
|
|
23014
|
+
|
|
23015
|
+
// src/tools/run-task.ts
|
|
23016
|
+
var taskResultSchema = external_exports.object({
|
|
23017
|
+
selected_model: external_exports.string().optional(),
|
|
23018
|
+
context_window_fit: external_exports.string().optional(),
|
|
23019
|
+
execution: external_exports.unknown().optional(),
|
|
23020
|
+
response: external_exports.unknown().optional(),
|
|
23021
|
+
telemetry: external_exports.unknown().optional()
|
|
23022
|
+
}).passthrough();
|
|
23023
|
+
var batchResultSchema = external_exports.object({
|
|
23024
|
+
results: external_exports.unknown().optional(),
|
|
23025
|
+
telemetry: external_exports.unknown().optional()
|
|
23026
|
+
}).passthrough();
|
|
23027
|
+
function validateTaskPayload(task, label) {
|
|
23028
|
+
const fail = (message) => label ? `${label}: ${message}` : message;
|
|
23029
|
+
if (task.scopeId === void 0 !== (task.scopeRevision === void 0)) {
|
|
23030
|
+
return fail("scopeId and scopeRevision must be supplied together.");
|
|
23031
|
+
}
|
|
23032
|
+
if (task.topLogprobs !== void 0 && task.logprobs !== true) return fail("`topLogprobs` requires `logprobs:true`.");
|
|
23033
|
+
if (task.task === "embedding") {
|
|
23034
|
+
if (task.input === void 0) return fail('task "embedding" requires `input`.');
|
|
23035
|
+
const chatControls = [
|
|
23036
|
+
task.prompt,
|
|
23037
|
+
task.systemPrompt,
|
|
23038
|
+
task.images,
|
|
23039
|
+
task.messages,
|
|
23040
|
+
task.tools,
|
|
23041
|
+
task.format,
|
|
23042
|
+
task.think,
|
|
23043
|
+
task.logprobs,
|
|
23044
|
+
task.topLogprobs
|
|
23045
|
+
];
|
|
23046
|
+
if (chatControls.some((value) => value !== void 0)) {
|
|
23047
|
+
return fail('task "embedding" accepts `input`, `options`, and routing fields; chat controls are not valid.');
|
|
23048
|
+
}
|
|
23049
|
+
return null;
|
|
23050
|
+
}
|
|
23051
|
+
if (task.input !== void 0 || task.returnEmbeddings !== void 0) {
|
|
23052
|
+
return fail('`input` and `returnEmbeddings` are valid only for task "embedding".');
|
|
23053
|
+
}
|
|
23054
|
+
if (task.prompt === void 0 && task.systemPrompt === void 0 && (task.messages === void 0 || task.messages.length === 0)) {
|
|
23055
|
+
return fail(`task "${task.task ?? "completion"}" requires \`prompt\`, \`systemPrompt\`, or \`messages\`.`);
|
|
23056
|
+
}
|
|
23057
|
+
if (task.images !== void 0 && (task.messages !== void 0 || task.prompt === void 0)) {
|
|
23058
|
+
return fail("Top-level `images` is valid only with `prompt`; put images inside messages instead.");
|
|
23059
|
+
}
|
|
23060
|
+
return null;
|
|
23061
|
+
}
|
|
23062
|
+
async function runBatch(tasks, maxParallelism, endpoint) {
|
|
23063
|
+
for (const item of tasks) {
|
|
23064
|
+
const problem = validateTaskPayload(item.task, `batch item ${item.id}`);
|
|
23065
|
+
if (problem) return errorResult(new Error(problem));
|
|
23066
|
+
}
|
|
23067
|
+
return withBatchTelemetry(parsedResult(await runTaskBatchRequest2(endpoint ?? DEFAULT_SERVE_ENDPOINT, {
|
|
23068
|
+
tasks: tasks.map(({ id, independent, task }) => ({
|
|
23069
|
+
id,
|
|
23070
|
+
independent,
|
|
23071
|
+
task: {
|
|
23072
|
+
task: task.task === void 0 ? task.scopeId === void 0 ? "completion" : void 0 : canonicalTaskKind(task.task),
|
|
23073
|
+
objective: task.objective ?? (task.scopeId === void 0 ? "balanced" : void 0),
|
|
23074
|
+
model: task.model,
|
|
23075
|
+
session_id: task.sessionId,
|
|
23076
|
+
scope_id: task.scopeId,
|
|
23077
|
+
scope_revision: task.scopeRevision,
|
|
23078
|
+
context_tokens: task.contextTokens,
|
|
23079
|
+
execution_preference: task.executionPreference,
|
|
23080
|
+
min_placement_evidence: task.minPlacementEvidence,
|
|
23081
|
+
required_capabilities: task.requiredCapabilities,
|
|
23082
|
+
priority: task.priority ?? "normal",
|
|
23083
|
+
max_wait_seconds: task.maxWaitSeconds,
|
|
23084
|
+
timeout_seconds: task.timeoutSeconds,
|
|
23085
|
+
stop_on_repetition: task.stopOnRepetition,
|
|
23086
|
+
prompt: task.prompt,
|
|
23087
|
+
images: task.images,
|
|
23088
|
+
messages: taskMessages(task),
|
|
23089
|
+
input: task.input,
|
|
23090
|
+
tools: task.tools,
|
|
23091
|
+
keep_alive: task.keepAlive,
|
|
23092
|
+
min_confidence: task.minConfidence,
|
|
23093
|
+
request_options: {
|
|
23094
|
+
format: task.format,
|
|
23095
|
+
think: task.think,
|
|
23096
|
+
options: task.options,
|
|
23097
|
+
logprobs: task.logprobs,
|
|
23098
|
+
top_logprobs: task.topLogprobs
|
|
23099
|
+
}
|
|
23100
|
+
}
|
|
23101
|
+
})),
|
|
23102
|
+
max_parallelism: maxParallelism
|
|
23103
|
+
})));
|
|
23104
|
+
}
|
|
23105
|
+
function registerRunTaskTool(server2) {
|
|
23106
|
+
server2.registerTool(
|
|
23107
|
+
"run_task",
|
|
23108
|
+
{
|
|
23109
|
+
description: "Use when: offloading caller-supplied work (chat, small tasks, code, tool calls, OCR/vision, embeddings) to a local model. Modes: single (`prompt`/`messages`, or `input` for embedding); `tasks` = up to 64 independent items, ordered results, `maxParallelism` caps dispatch; `preview:true` decides only; `warm:true` preloads `model`. Images: base64 `images` + the model's own prompt; `stopOnRepetition` ends a looping answer early, keeping its text. Do not use when: it needs file access (pass content, or delegate_research) or high-stakes judgment. Returns: answer text; receipts in structuredContent.",
|
|
23110
|
+
inputSchema: external_exports.object({
|
|
23111
|
+
endpoint: endpointParam,
|
|
23112
|
+
task: taskParam.removeDefault().optional().describe("caller owns prompts and output format."),
|
|
23113
|
+
objective: objectiveParam,
|
|
23114
|
+
model: external_exports.string().min(1).optional(),
|
|
23115
|
+
sessionId: external_exports.string().uuid().optional(),
|
|
23116
|
+
scopeId: external_exports.string().uuid().optional(),
|
|
23117
|
+
scopeRevision: external_exports.number().int().nonnegative().optional(),
|
|
23118
|
+
contextTokens: external_exports.number().int().positive().optional().describe("Total input + output window (num_ctx)."),
|
|
23119
|
+
executionPreference: executionPreferenceParam,
|
|
23120
|
+
minPlacementEvidence: minPlacementEvidenceParam,
|
|
23121
|
+
requiredCapabilities: requiredCapabilitiesParam,
|
|
23122
|
+
prompt: external_exports.string().min(1).optional(),
|
|
23123
|
+
systemPrompt: systemPromptParam,
|
|
23124
|
+
images: external_exports.array(external_exports.string().min(1)).min(1).optional().describe("base64, no data-URI prefix; prompt mode only; needs `model` or [policies.vision]"),
|
|
23125
|
+
messages: messagesParam.describe(
|
|
23126
|
+
"Caller system prompts; no injected task instructions. Overrides prompt; extra fields preserved."
|
|
23127
|
+
),
|
|
23128
|
+
input: external_exports.union([external_exports.string().min(1), external_exports.array(external_exports.string().min(1)).min(1)]).optional(),
|
|
23129
|
+
tools: localToolsParam,
|
|
23130
|
+
keepAlive: external_exports.string().min(1).optional().describe('"0" unloads now, "-1" pins; omitted = adaptive finite TTL'),
|
|
23131
|
+
format: external_exports.union([external_exports.literal("json"), external_exports.record(external_exports.unknown())]).optional(),
|
|
23132
|
+
think: external_exports.union([external_exports.boolean(), external_exports.enum(["low", "medium", "high"])]).optional(),
|
|
23133
|
+
options: external_exports.record(external_exports.unknown()).optional().describe("num_ctx/contextTokens and num_gpu/placement are routing-owned; num_predict caps output."),
|
|
23134
|
+
logprobs: external_exports.boolean().optional(),
|
|
23135
|
+
topLogprobs: external_exports.number().int().nonnegative().optional(),
|
|
23136
|
+
minConfidence: minConfidenceParam,
|
|
23137
|
+
priority: external_exports.enum(["interactive", "normal", "background"]).optional(),
|
|
23138
|
+
maxWaitSeconds: external_exports.number().int().positive().optional(),
|
|
23139
|
+
timeoutSeconds: external_exports.number().int().positive().optional(),
|
|
23140
|
+
stopOnRepetition: external_exports.boolean().optional().describe("Stream and stop once output repeats itself (a loop to num_predict); keeps all generated text"),
|
|
23141
|
+
defer: external_exports.boolean().optional(),
|
|
23142
|
+
returnEmbeddings: external_exports.boolean().optional(),
|
|
23143
|
+
preview: external_exports.boolean().optional().describe(
|
|
23144
|
+
"true = routing fields only; rejects payloads and runtime controls; never executes"
|
|
23145
|
+
),
|
|
23146
|
+
warm: external_exports.boolean().optional().describe("Load exact `model`; no prompt"),
|
|
23147
|
+
tasks: external_exports.array(batchItemParam).min(1).max(64).optional().describe("Batch mode: independent items; per-item fields go inside each item's `task`"),
|
|
23148
|
+
maxParallelism: external_exports.number().int().positive().max(64).optional().describe("Batch mode only")
|
|
23149
|
+
}).strict(),
|
|
23150
|
+
outputSchema: taskResultSchema.merge(batchResultSchema),
|
|
23151
|
+
annotations: { destructiveHint: false }
|
|
23152
|
+
},
|
|
23153
|
+
async ({
|
|
23154
|
+
endpoint,
|
|
23155
|
+
task,
|
|
23156
|
+
objective,
|
|
23157
|
+
model,
|
|
23158
|
+
sessionId,
|
|
23159
|
+
scopeId,
|
|
23160
|
+
scopeRevision,
|
|
23161
|
+
contextTokens,
|
|
23162
|
+
executionPreference,
|
|
23163
|
+
minPlacementEvidence,
|
|
23164
|
+
requiredCapabilities,
|
|
23165
|
+
prompt,
|
|
23166
|
+
systemPrompt,
|
|
23167
|
+
images,
|
|
23168
|
+
messages,
|
|
23169
|
+
input,
|
|
23170
|
+
tools,
|
|
23171
|
+
keepAlive,
|
|
23172
|
+
format,
|
|
23173
|
+
think,
|
|
23174
|
+
options,
|
|
23175
|
+
logprobs,
|
|
23176
|
+
topLogprobs,
|
|
23177
|
+
minConfidence,
|
|
23178
|
+
priority,
|
|
23179
|
+
maxWaitSeconds,
|
|
23180
|
+
timeoutSeconds,
|
|
23181
|
+
stopOnRepetition,
|
|
23182
|
+
defer,
|
|
23183
|
+
returnEmbeddings,
|
|
23184
|
+
preview,
|
|
23185
|
+
warm,
|
|
23186
|
+
tasks,
|
|
23187
|
+
maxParallelism
|
|
23188
|
+
}) => {
|
|
23189
|
+
try {
|
|
23190
|
+
const singleFields = {
|
|
23191
|
+
task,
|
|
23192
|
+
objective,
|
|
23193
|
+
model,
|
|
23194
|
+
sessionId,
|
|
23195
|
+
scopeId,
|
|
23196
|
+
scopeRevision,
|
|
23197
|
+
contextTokens,
|
|
23198
|
+
executionPreference,
|
|
23199
|
+
minPlacementEvidence,
|
|
23200
|
+
requiredCapabilities,
|
|
23201
|
+
prompt,
|
|
23202
|
+
systemPrompt,
|
|
23203
|
+
images,
|
|
23204
|
+
messages,
|
|
23205
|
+
input,
|
|
23206
|
+
tools,
|
|
23207
|
+
keepAlive,
|
|
23208
|
+
format,
|
|
23209
|
+
think,
|
|
23210
|
+
options,
|
|
23211
|
+
logprobs,
|
|
23212
|
+
topLogprobs,
|
|
23213
|
+
minConfidence,
|
|
23214
|
+
priority,
|
|
23215
|
+
maxWaitSeconds,
|
|
23216
|
+
timeoutSeconds,
|
|
23217
|
+
stopOnRepetition,
|
|
23218
|
+
defer,
|
|
23219
|
+
returnEmbeddings,
|
|
23220
|
+
preview,
|
|
23221
|
+
warm
|
|
23222
|
+
};
|
|
23223
|
+
if (tasks !== void 0) {
|
|
23224
|
+
const stray = Object.entries(singleFields).filter(([, value]) => value !== void 0).map(([name]) => name);
|
|
23225
|
+
const unsupported = stray.filter((name) => !BATCH_TASK_FIELDS.has(name)).map((name) => `\`${name}\``);
|
|
23226
|
+
if (unsupported.length > 0) {
|
|
23227
|
+
return errorResult(new Error(`${unsupported.join(", ")} ${unsupported.length === 1 ? "is" : "are"} not supported with \`tasks\`.`));
|
|
23228
|
+
}
|
|
23229
|
+
const movable = stray.map((name) => `\`${name}\``);
|
|
23230
|
+
if (movable.length > 0) {
|
|
23231
|
+
return errorResult(new Error(`With \`tasks\`, put ${movable.join(", ")} inside each item's \`task\`.`));
|
|
23232
|
+
}
|
|
23233
|
+
return await runBatch(tasks, maxParallelism, endpoint);
|
|
23234
|
+
}
|
|
23235
|
+
if (maxParallelism !== void 0) return errorResult(new Error("`maxParallelism` is valid only with `tasks`."));
|
|
23236
|
+
if (warm) {
|
|
23237
|
+
const allowed = /* @__PURE__ */ new Set([
|
|
23238
|
+
"warm",
|
|
23239
|
+
"model",
|
|
23240
|
+
"task",
|
|
23241
|
+
"contextTokens",
|
|
23242
|
+
"executionPreference",
|
|
23243
|
+
"minPlacementEvidence",
|
|
23244
|
+
"priority",
|
|
23245
|
+
"keepAlive",
|
|
23246
|
+
"maxWaitSeconds",
|
|
23247
|
+
"timeoutSeconds",
|
|
23248
|
+
"defer"
|
|
23249
|
+
]);
|
|
23250
|
+
const stray = Object.entries(singleFields).filter(([name, value]) => value !== void 0 && !allowed.has(name)).map(([name]) => `\`${name}\``);
|
|
23251
|
+
if (stray.length > 0) return errorResult(new Error(`\`warm:true\` loads a model without generation; remove ${stray.join(", ")}.`));
|
|
23252
|
+
if (model === void 0) return errorResult(new Error("`warm:true` requires an exact installed `model`."));
|
|
23253
|
+
if (task === "embedding") return errorResult(new Error("`warm:true` supports chat models; embedding warming is unsupported."));
|
|
23254
|
+
return parsedResult(await warmModelRequest2(endpoint, {
|
|
23255
|
+
model,
|
|
23256
|
+
task: task === void 0 ? void 0 : canonicalTaskKind(task),
|
|
23257
|
+
context_tokens: contextTokens,
|
|
23258
|
+
execution_preference: executionPreference,
|
|
23259
|
+
min_placement_evidence: minPlacementEvidence,
|
|
23260
|
+
priority,
|
|
23261
|
+
keep_alive: keepAlive,
|
|
23262
|
+
max_wait_seconds: maxWaitSeconds,
|
|
23263
|
+
timeout_seconds: timeoutSeconds,
|
|
23264
|
+
defer
|
|
23265
|
+
}));
|
|
23266
|
+
}
|
|
23267
|
+
const canonicalTask = canonicalTaskKind(task ?? "completion");
|
|
23268
|
+
if (preview) {
|
|
23269
|
+
const executionOnlyFields = [
|
|
23270
|
+
["scopeId", scopeId],
|
|
23271
|
+
["scopeRevision", scopeRevision],
|
|
23272
|
+
["prompt", prompt],
|
|
23273
|
+
["systemPrompt", systemPrompt],
|
|
23274
|
+
["images", images],
|
|
23275
|
+
["messages", messages],
|
|
23276
|
+
["input", input],
|
|
23277
|
+
["tools", tools],
|
|
23278
|
+
["keepAlive", keepAlive],
|
|
23279
|
+
["format", format],
|
|
23280
|
+
["think", think],
|
|
23281
|
+
["options", options],
|
|
23282
|
+
["logprobs", logprobs],
|
|
23283
|
+
["topLogprobs", topLogprobs],
|
|
23284
|
+
["returnEmbeddings", returnEmbeddings],
|
|
23285
|
+
["maxWaitSeconds", maxWaitSeconds],
|
|
23286
|
+
["priority", priority],
|
|
23287
|
+
["timeoutSeconds", timeoutSeconds],
|
|
23288
|
+
["stopOnRepetition", stopOnRepetition],
|
|
23289
|
+
["defer", defer]
|
|
23290
|
+
].filter(([, value]) => value !== void 0).map(([name]) => `\`${name}\``);
|
|
23291
|
+
if (executionOnlyFields.length > 0) {
|
|
23292
|
+
return errorResult(
|
|
23293
|
+
new Error(
|
|
23294
|
+
`\`preview:true\` accepts routing fields only; remove execution-only fields ${executionOnlyFields.join(", ")}. Use \`requiredCapabilities:["tools"]\` to preview tool capability, then make a separate execution call with the payload.`
|
|
23295
|
+
)
|
|
23296
|
+
);
|
|
23297
|
+
}
|
|
23298
|
+
}
|
|
23299
|
+
const effectiveRequiredCapabilities = tools === void 0 ? requiredCapabilities : withRequiredCapability(requiredCapabilities, "tools");
|
|
23300
|
+
if (preview) {
|
|
23301
|
+
return parsedResult(
|
|
23302
|
+
await route2(endpoint, canonicalTask, objective, model, sessionId, contextTokens, effectiveRequiredCapabilities, minConfidence, executionPreference, minPlacementEvidence)
|
|
23303
|
+
);
|
|
23304
|
+
}
|
|
23305
|
+
const problem = validateTaskPayload({
|
|
23306
|
+
task,
|
|
23307
|
+
scopeId,
|
|
23308
|
+
scopeRevision,
|
|
23309
|
+
prompt,
|
|
23310
|
+
systemPrompt,
|
|
23311
|
+
messages,
|
|
23312
|
+
images,
|
|
23313
|
+
input,
|
|
23314
|
+
tools,
|
|
23315
|
+
format,
|
|
23316
|
+
think,
|
|
23317
|
+
logprobs,
|
|
23318
|
+
topLogprobs,
|
|
23319
|
+
returnEmbeddings
|
|
23320
|
+
});
|
|
23321
|
+
if (problem) return errorResult(new Error(problem));
|
|
23322
|
+
if (minConfidence && defer && scopeId === void 0) {
|
|
23323
|
+
const decision = parsedResult(
|
|
23324
|
+
await route2(endpoint, canonicalTask, objective, model, sessionId, contextTokens, effectiveRequiredCapabilities, minConfidence, executionPreference, minPlacementEvidence)
|
|
23325
|
+
);
|
|
23326
|
+
if (!isStructuredSuccess(decision)) return decision;
|
|
23327
|
+
}
|
|
23328
|
+
const result = parsedResult(
|
|
23329
|
+
await runTaskRequest2(endpoint ?? DEFAULT_SERVE_ENDPOINT, {
|
|
23330
|
+
task: scopeId !== void 0 && task === void 0 ? void 0 : canonicalTask,
|
|
23331
|
+
objective: objective ?? (scopeId === void 0 ? "balanced" : void 0),
|
|
23332
|
+
model,
|
|
23333
|
+
session_id: sessionId,
|
|
23334
|
+
scope_id: scopeId,
|
|
23335
|
+
scope_revision: scopeRevision,
|
|
23336
|
+
context_tokens: contextTokens,
|
|
23337
|
+
required_capabilities: effectiveRequiredCapabilities,
|
|
23338
|
+
prompt,
|
|
23339
|
+
images,
|
|
23340
|
+
messages: taskMessages({ systemPrompt, messages, prompt, images }),
|
|
23341
|
+
input,
|
|
23342
|
+
tools,
|
|
23343
|
+
keep_alive: keepAlive,
|
|
23344
|
+
min_confidence: minConfidence,
|
|
23345
|
+
priority: priority ?? "normal",
|
|
23346
|
+
max_wait_seconds: maxWaitSeconds,
|
|
23347
|
+
timeout_seconds: timeoutSeconds,
|
|
23348
|
+
stop_on_repetition: stopOnRepetition,
|
|
23349
|
+
defer: defer ?? false,
|
|
23350
|
+
execution_preference: executionPreference,
|
|
23351
|
+
min_placement_evidence: minPlacementEvidence,
|
|
23352
|
+
request_options: {
|
|
23353
|
+
format,
|
|
23354
|
+
think,
|
|
23355
|
+
options,
|
|
23356
|
+
logprobs,
|
|
23357
|
+
top_logprobs: topLogprobs
|
|
23358
|
+
}
|
|
23359
|
+
})
|
|
22932
23360
|
);
|
|
23361
|
+
if (isStructuredSuccess(result) && result.structuredContent.deferred === true) return result;
|
|
23362
|
+
if (!returnEmbeddings && isStructuredSuccess(result)) {
|
|
23363
|
+
const trimmed = summarizeEmbeddings(result.structuredContent);
|
|
23364
|
+
if (trimmed) return withTaskTelemetry(structuredResult(trimmed));
|
|
23365
|
+
}
|
|
23366
|
+
return withTaskTelemetry(result);
|
|
23367
|
+
} catch (error2) {
|
|
23368
|
+
return errorResult(error2);
|
|
22933
23369
|
}
|
|
22934
|
-
|
|
22935
|
-
|
|
22936
|
-
|
|
22937
|
-
|
|
22938
|
-
|
|
22939
|
-
|
|
22940
|
-
|
|
22941
|
-
|
|
22942
|
-
|
|
22943
|
-
|
|
22944
|
-
|
|
22945
|
-
|
|
22946
|
-
|
|
22947
|
-
|
|
22948
|
-
|
|
22949
|
-
|
|
22950
|
-
|
|
22951
|
-
|
|
22952
|
-
|
|
22953
|
-
|
|
22954
|
-
|
|
22955
|
-
|
|
22956
|
-
|
|
22957
|
-
|
|
22958
|
-
|
|
22959
|
-
|
|
22960
|
-
|
|
22961
|
-
|
|
22962
|
-
|
|
23370
|
+
}
|
|
23371
|
+
);
|
|
23372
|
+
}
|
|
23373
|
+
|
|
23374
|
+
// src/tools/task-jobs.ts
|
|
23375
|
+
function registerTaskJobsTool(server2) {
|
|
23376
|
+
server2.registerTool(
|
|
23377
|
+
"task_jobs",
|
|
23378
|
+
{
|
|
23379
|
+
description: "Use when: following work submitted with run_task {defer:true}: list, get (result), cancel, or remove a job. Do not use when: submitting new work or killing a whole session. Returns: job status/result; remove cancels first.",
|
|
23380
|
+
inputSchema: external_exports.object({
|
|
23381
|
+
endpoint: endpointParam,
|
|
23382
|
+
action: external_exports.enum(["list", "get", "cancel", "remove"]),
|
|
23383
|
+
jobId: external_exports.string().uuid().optional(),
|
|
23384
|
+
returnEmbeddings: external_exports.boolean().optional()
|
|
23385
|
+
}).strict(),
|
|
23386
|
+
outputSchema: objectResultSchema,
|
|
23387
|
+
annotations: { destructiveHint: true }
|
|
23388
|
+
},
|
|
23389
|
+
async ({ endpoint, action, jobId, returnEmbeddings }) => {
|
|
23390
|
+
try {
|
|
23391
|
+
if (action === "list" ? jobId !== void 0 : jobId === void 0) {
|
|
23392
|
+
return errorResult(new Error("Use jobId only with get/cancel/remove; all require a jobId."));
|
|
23393
|
+
}
|
|
23394
|
+
if (returnEmbeddings !== void 0 && action !== "get") {
|
|
23395
|
+
return errorResult(new Error("returnEmbeddings is valid only with get."));
|
|
23396
|
+
}
|
|
23397
|
+
const result = parsedResult(action === "list" ? await listTaskJobs2(endpoint) : action === "get" ? await getTaskJob2(endpoint, jobId) : action === "cancel" ? await cancelTaskJob2(endpoint, jobId) : await removeTaskJob2(endpoint, jobId));
|
|
23398
|
+
if (!returnEmbeddings && isStructuredSuccess(result)) {
|
|
23399
|
+
const job = result.structuredContent.job;
|
|
23400
|
+
if (job?.result && typeof job.result === "object") {
|
|
23401
|
+
const trimmed = summarizeEmbeddings(job.result);
|
|
23402
|
+
if (trimmed) return structuredResult({ ...result.structuredContent, job: { ...job, result: trimmed } });
|
|
23403
|
+
}
|
|
23404
|
+
}
|
|
23405
|
+
return result;
|
|
23406
|
+
} catch (error2) {
|
|
23407
|
+
return errorResult(error2);
|
|
23408
|
+
}
|
|
23409
|
+
}
|
|
23410
|
+
);
|
|
23411
|
+
}
|
|
23412
|
+
|
|
23413
|
+
// src/tools/ollama-lifecycle.ts
|
|
23414
|
+
var manageResultSchema = external_exports.object({
|
|
23415
|
+
status: external_exports.string().optional(),
|
|
23416
|
+
progress: external_exports.unknown().optional()
|
|
23417
|
+
}).passthrough();
|
|
23418
|
+
var deleteResultSchema = external_exports.object({
|
|
23419
|
+
deleted: external_exports.string()
|
|
23420
|
+
}).passthrough();
|
|
23421
|
+
function summarizeOllamaPullStream(text) {
|
|
23422
|
+
const events = [];
|
|
23423
|
+
for (const line of text.split(/\r?\n/)) {
|
|
23424
|
+
const trimmed = line.trim();
|
|
23425
|
+
if (!trimmed) continue;
|
|
23426
|
+
try {
|
|
23427
|
+
const parsed = JSON.parse(trimmed);
|
|
23428
|
+
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
23429
|
+
events.push(parsed);
|
|
23430
|
+
}
|
|
23431
|
+
} catch {
|
|
23432
|
+
}
|
|
23433
|
+
}
|
|
23434
|
+
if (events.length === 0) {
|
|
23435
|
+
if (!text.trim()) return { status: "empty" };
|
|
23436
|
+
try {
|
|
23437
|
+
const parsed = JSON.parse(text);
|
|
23438
|
+
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
23439
|
+
return parsed;
|
|
23440
|
+
}
|
|
23441
|
+
} catch {
|
|
23442
|
+
return { status: "unparsed", raw: clipText(text, 500) };
|
|
23443
|
+
}
|
|
23444
|
+
return { status: "unparsed", raw: clipText(text, 500) };
|
|
23445
|
+
}
|
|
23446
|
+
const last = events[events.length - 1] ?? {};
|
|
23447
|
+
const withBytes = [...events].reverse().find((event) => typeof event.total === "number" && event.total > 0);
|
|
23448
|
+
const completed = typeof withBytes?.completed === "number" ? withBytes.completed : null;
|
|
23449
|
+
const total = typeof withBytes?.total === "number" ? withBytes.total : null;
|
|
23450
|
+
const percent = completed != null && total != null && total > 0 ? Math.round(completed / total * 1e3) / 10 : null;
|
|
23451
|
+
const status2 = typeof last.error === "string" && last.error ? "error" : last.status;
|
|
23452
|
+
return {
|
|
23453
|
+
...last,
|
|
23454
|
+
...status2 === void 0 ? {} : { status: status2 },
|
|
23455
|
+
progress: {
|
|
23456
|
+
events: events.length,
|
|
23457
|
+
completed,
|
|
23458
|
+
total,
|
|
23459
|
+
percent,
|
|
23460
|
+
lastStatus: last.status ?? null
|
|
23461
|
+
}
|
|
23462
|
+
};
|
|
23463
|
+
}
|
|
23464
|
+
async function ollamaPull(endpoint, model, timeoutSeconds, options = {}) {
|
|
23465
|
+
const raw = await ollamaFetch(endpoint, "/api/pull", {
|
|
23466
|
+
method: "POST",
|
|
23467
|
+
body: { model, stream: true },
|
|
23468
|
+
timeoutMs: (timeoutSeconds ?? DEFAULT_PULL_TIMEOUT_SECONDS) * 1e3,
|
|
23469
|
+
parse: "ndjson",
|
|
23470
|
+
signal: options.signal,
|
|
23471
|
+
onLine: options.onProgress
|
|
23472
|
+
});
|
|
23473
|
+
if (typeof raw === "string") return summarizeOllamaPullStream(raw);
|
|
23474
|
+
if (raw && typeof raw === "object" && !Array.isArray(raw)) return raw;
|
|
23475
|
+
return { status: "empty" };
|
|
23476
|
+
}
|
|
23477
|
+
function pullProgressReporter(extra) {
|
|
23478
|
+
const token = extra._meta?.progressToken;
|
|
23479
|
+
if (token === void 0) return void 0;
|
|
23480
|
+
let lastSent = 0;
|
|
23481
|
+
let step = 0;
|
|
23482
|
+
return (event) => {
|
|
23483
|
+
const now = Date.now();
|
|
23484
|
+
if (now - lastSent < 500 && event.status !== "success") return;
|
|
23485
|
+
lastSent = now;
|
|
23486
|
+
step += 1;
|
|
23487
|
+
const completed = typeof event.completed === "number" ? event.completed : void 0;
|
|
23488
|
+
const total = typeof event.total === "number" ? event.total : void 0;
|
|
23489
|
+
void extra.sendNotification({
|
|
23490
|
+
method: "notifications/progress",
|
|
23491
|
+
params: {
|
|
23492
|
+
progressToken: token,
|
|
23493
|
+
progress: completed ?? step,
|
|
23494
|
+
...completed !== void 0 && total !== void 0 ? { total } : {},
|
|
23495
|
+
message: String(event.status ?? "pulling")
|
|
23496
|
+
}
|
|
23497
|
+
}).catch(() => void 0);
|
|
23498
|
+
};
|
|
23499
|
+
}
|
|
23500
|
+
function registerOllamaLifecycleTools(server2) {
|
|
23501
|
+
server2.registerTool(
|
|
23502
|
+
"ollama_manage",
|
|
23503
|
+
{
|
|
23504
|
+
description: "Use when: a human approved pulling one exact tag (action:pull) or unloading a loaded model (action:stop). Do not use when: deleting (ollama_delete), searching (models), or preloading (run_task {warm:true}). Returns: receipt; timeoutSeconds is pull-only.",
|
|
23505
|
+
inputSchema: external_exports.object({
|
|
23506
|
+
action: external_exports.enum(["pull", "stop"]),
|
|
23507
|
+
model: external_exports.string().min(1),
|
|
23508
|
+
ollamaEndpoint: ollamaEndpointParam,
|
|
23509
|
+
timeoutSeconds: external_exports.number().int().positive().optional()
|
|
23510
|
+
}).strict(),
|
|
23511
|
+
outputSchema: manageResultSchema,
|
|
23512
|
+
annotations: { destructiveHint: false }
|
|
23513
|
+
},
|
|
23514
|
+
async ({ action, model, ollamaEndpoint, timeoutSeconds }, extra) => {
|
|
23515
|
+
try {
|
|
23516
|
+
if (action === "stop" && timeoutSeconds !== void 0) {
|
|
23517
|
+
return errorResult(new Error('`timeoutSeconds` is valid only for action "pull".'));
|
|
23518
|
+
}
|
|
23519
|
+
const data = action === "pull" ? await ollamaPull(ollamaEndpoint, model, timeoutSeconds, {
|
|
23520
|
+
signal: extra.signal,
|
|
23521
|
+
onProgress: pullProgressReporter(extra)
|
|
23522
|
+
}) : await ollamaFetch(ollamaEndpoint, "/api/generate", {
|
|
23523
|
+
method: "POST",
|
|
23524
|
+
body: { model, keep_alive: 0 },
|
|
23525
|
+
signal: extra.signal
|
|
23526
|
+
});
|
|
23527
|
+
const payload = data;
|
|
23528
|
+
if (typeof payload.error === "string" && payload.error) {
|
|
23529
|
+
return errorResult(new Error(`${action} ${model} failed: ${payload.error}`));
|
|
23530
|
+
}
|
|
23531
|
+
return structuredResult(payload);
|
|
23532
|
+
} catch (error2) {
|
|
23533
|
+
return errorResult(error2);
|
|
23534
|
+
}
|
|
23535
|
+
}
|
|
23536
|
+
);
|
|
23537
|
+
server2.registerTool(
|
|
23538
|
+
"ollama_delete",
|
|
23539
|
+
{
|
|
23540
|
+
description: "DESTRUCTIVE AND IRREVERSIBLE. Use when: a human explicitly names the exact installed tag to delete. Do not use when: you inferred the cleanup yourself, or only want to unload (ollama_manage stop). Returns: the deleted tag.",
|
|
23541
|
+
inputSchema: external_exports.object({
|
|
23542
|
+
model: external_exports.string().min(1),
|
|
23543
|
+
ollamaEndpoint: ollamaEndpointParam
|
|
23544
|
+
}).strict(),
|
|
23545
|
+
outputSchema: deleteResultSchema,
|
|
23546
|
+
annotations: { destructiveHint: true }
|
|
23547
|
+
},
|
|
23548
|
+
async ({ model, ollamaEndpoint }) => {
|
|
23549
|
+
try {
|
|
23550
|
+
await ollamaFetch(ollamaEndpoint, "/api/delete", {
|
|
23551
|
+
method: "DELETE",
|
|
23552
|
+
body: { model }
|
|
23553
|
+
});
|
|
23554
|
+
return structuredResult({ deleted: model });
|
|
23555
|
+
} catch (error2) {
|
|
23556
|
+
return errorResult(error2);
|
|
23557
|
+
}
|
|
23558
|
+
}
|
|
23559
|
+
);
|
|
23560
|
+
}
|
|
23561
|
+
|
|
23562
|
+
// src/tools/delegate-research.ts
|
|
23563
|
+
import { existsSync as existsSync5 } from "node:fs";
|
|
23564
|
+
import { mkdtemp, readFile as readFile2, rm, writeFile } from "node:fs/promises";
|
|
23565
|
+
import { tmpdir } from "node:os";
|
|
23566
|
+
import path5 from "node:path";
|
|
23567
|
+
|
|
23568
|
+
// src/delegate.ts
|
|
23569
|
+
import { existsSync as existsSync4, readFileSync, realpathSync, statSync } from "node:fs";
|
|
23570
|
+
import path4 from "node:path";
|
|
23571
|
+
function researchErrorResult(error2, diagnostic) {
|
|
23572
|
+
const result = errorResult(error2);
|
|
23573
|
+
const summary = diagnostic ?? result.content[0].text;
|
|
23574
|
+
return {
|
|
23575
|
+
...result,
|
|
23576
|
+
content: [{ type: "text", text: summary }],
|
|
23577
|
+
structuredContent: {
|
|
23578
|
+
...result.structuredContent ?? { error: summary },
|
|
23579
|
+
answer: "",
|
|
23580
|
+
summary,
|
|
23581
|
+
verification: {
|
|
23582
|
+
recommendation: "escalate",
|
|
23583
|
+
grounded: false,
|
|
23584
|
+
why: "The research request failed; no usable answer was produced."
|
|
23585
|
+
}
|
|
23586
|
+
}
|
|
23587
|
+
};
|
|
23588
|
+
}
|
|
23589
|
+
var MODEL_EVIDENCE = (() => {
|
|
23590
|
+
const configured = process.env.FREELLAMA_MCP_MODEL_EVIDENCE;
|
|
23591
|
+
const candidates = [
|
|
23592
|
+
configured,
|
|
23593
|
+
path4.join(REPO_ROOT, "packages/mcp/evidence/model-evidence.json")
|
|
23594
|
+
].filter((x) => Boolean(x));
|
|
23595
|
+
for (const file of candidates) {
|
|
23596
|
+
try {
|
|
23597
|
+
if (!existsSync4(file)) continue;
|
|
23598
|
+
const parsed = JSON.parse(readFileSync(file, "utf8"));
|
|
23599
|
+
return parsed.models ?? {};
|
|
22963
23600
|
} catch (error2) {
|
|
22964
|
-
|
|
22965
|
-
|
|
22966
|
-
|
|
23601
|
+
process.stderr.write(
|
|
23602
|
+
`freellama-mcp: ignoring unreadable model evidence ${file}: ${error2 instanceof Error ? error2.message : String(error2)}
|
|
23603
|
+
`
|
|
23604
|
+
);
|
|
22967
23605
|
}
|
|
22968
23606
|
}
|
|
23607
|
+
return {};
|
|
23608
|
+
})();
|
|
23609
|
+
var STRONG_MODEL_HINT = "Re-run with a model graded strong in packages/mcp/evidence/model-evidence.json";
|
|
23610
|
+
function assessDelegatedAnswer(question, evidenceCount, model) {
|
|
23611
|
+
const grounded = evidenceCount > 0;
|
|
23612
|
+
const evidence = MODEL_EVIDENCE[model];
|
|
23613
|
+
if (evidence?.grade === "unusable") {
|
|
23614
|
+
return {
|
|
23615
|
+
recommendation: "escalate",
|
|
23616
|
+
grounded,
|
|
23617
|
+
why: `${model} is not viable for research on this machine (${evidence.note}). It answers fast and confidently while being wrong \u2014 a fast wrong answer is not a speed win. ${STRONG_MODEL_HINT}, or answer it yourself.`,
|
|
23618
|
+
measuredBaseRate: `${model}: ${evidence.note}`
|
|
23619
|
+
};
|
|
23620
|
+
}
|
|
23621
|
+
if (!evidence) {
|
|
23622
|
+
return {
|
|
23623
|
+
recommendation: "verify",
|
|
23624
|
+
grounded,
|
|
23625
|
+
why: `${model} has no measured accuracy in this repo's benchmarks, so no base rate applies to this answer. Treat it as unverified until it has been evaluated \u2014 accuracy fell off a cliff below ~12B in the models that were measured.`,
|
|
23626
|
+
measuredBaseRate: "no measured base rate for this model"
|
|
23627
|
+
};
|
|
23628
|
+
}
|
|
23629
|
+
if (evidence.grade === "weak" && grounded) {
|
|
23630
|
+
return {
|
|
23631
|
+
recommendation: "verify",
|
|
23632
|
+
grounded,
|
|
23633
|
+
why: `${model} holds up on simple single-file lookups but not beyond (${evidence.note}). Check the evidence trail, or ${STRONG_MODEL_HINT.toLowerCase()} if the answer matters.`,
|
|
23634
|
+
measuredBaseRate: `${model}: ${evidence.note}`
|
|
23635
|
+
};
|
|
23636
|
+
}
|
|
23637
|
+
if (!grounded) {
|
|
23638
|
+
return {
|
|
23639
|
+
recommendation: "escalate",
|
|
23640
|
+
grounded: false,
|
|
23641
|
+
why: "The model answered without reading a single file, so this is parametric recall, not research \u2014 the failure mode where `run_task` was verified inventing wrong facts about this project's own architecture. Re-ask with a narrower question, or answer it yourself.",
|
|
23642
|
+
measuredBaseRate: "ungrounded answers have no measured accuracy \u2014 they were never the tested path"
|
|
23643
|
+
};
|
|
23644
|
+
}
|
|
23645
|
+
const judgmentSignals = /\b(should (we|i|you)|better|best|worth|review|assess|evaluate|improve|opinion|recommend|why is|is it (good|safe|correct)|design|refactor)\b/i;
|
|
23646
|
+
if (judgmentSignals.test(question)) {
|
|
23647
|
+
return {
|
|
23648
|
+
recommendation: "verify",
|
|
23649
|
+
grounded: true,
|
|
23650
|
+
why: "The question reads as a judgment call (keyword heuristic). Measured accuracy on judgment calls was well below grounded lookups, and the tone is identical either way. Check the evidence trail against the claim before acting on it.",
|
|
23651
|
+
measuredBaseRate: `${model}: ${evidence.note}; measured on lookups, not judgment calls`
|
|
23652
|
+
};
|
|
23653
|
+
}
|
|
23654
|
+
if (evidenceCount > 5) {
|
|
23655
|
+
return {
|
|
23656
|
+
recommendation: "verify",
|
|
23657
|
+
grounded: true,
|
|
23658
|
+
why: `${evidenceCount} tool calls is outside the 1-5 file envelope this tool was measured on. Wide searches are where it drifts; spot-check the evidence trail.`,
|
|
23659
|
+
measuredBaseRate: `${model}: ${evidence.note}; measured within a 1-5 file scope`
|
|
23660
|
+
};
|
|
23661
|
+
}
|
|
23662
|
+
return {
|
|
23663
|
+
recommendation: "accept",
|
|
23664
|
+
grounded: true,
|
|
23665
|
+
why: `Grounded in ${evidenceCount} tool call(s) within the measured 1-5 file envelope, the question is lookup-shaped, and ${model} is measured strong for this (${evidence.note}).`,
|
|
23666
|
+
measuredBaseRate: `${model}: ${evidence.note}`
|
|
23667
|
+
};
|
|
23668
|
+
}
|
|
23669
|
+
function extractExistingWorkspacePath(command, workspace) {
|
|
23670
|
+
let root;
|
|
23671
|
+
try {
|
|
23672
|
+
root = realpathSync(workspace);
|
|
23673
|
+
} catch {
|
|
23674
|
+
return null;
|
|
23675
|
+
}
|
|
23676
|
+
const tokens = command.match(/(?:[^\s"'\\]+|"(?:\\.|[^"])*"|'[^']*')+/g) ?? [];
|
|
23677
|
+
const patternFlags = /* @__PURE__ */ new Set(["-name", "-iname", "-path", "-ipath", "-regex", "-iregex", "-wholename"]);
|
|
23678
|
+
for (let index = 0; index < tokens.length; index += 1) {
|
|
23679
|
+
const raw = tokens[index] ?? "";
|
|
23680
|
+
if (index > 0 && patternFlags.has(tokens[index - 1] ?? "")) continue;
|
|
23681
|
+
const token = raw.replace(/^["']|["']$/g, "").replace(/[|;&)]+$/g, "").replace(/:\d+(?::\d+)?$/, "");
|
|
23682
|
+
if (!token || token.startsWith("-") || /[*?$`]/.test(token)) continue;
|
|
23683
|
+
const candidate = path4.isAbsolute(token) ? token : path4.resolve(root, token);
|
|
23684
|
+
if (!existsSync4(candidate)) continue;
|
|
23685
|
+
try {
|
|
23686
|
+
const resolved = realpathSync(candidate);
|
|
23687
|
+
const relative = path4.relative(root, resolved);
|
|
23688
|
+
const withinRoot = relative && relative !== ".." && !relative.startsWith(`..${path4.sep}`);
|
|
23689
|
+
if (withinRoot && statSync(resolved).isFile()) return relative;
|
|
23690
|
+
} catch {
|
|
23691
|
+
}
|
|
23692
|
+
}
|
|
23693
|
+
return null;
|
|
23694
|
+
}
|
|
23695
|
+
var adapterCallSchema = external_exports.object({
|
|
23696
|
+
raw_name: external_exports.string().optional(),
|
|
23697
|
+
status: external_exports.string().optional(),
|
|
23698
|
+
arguments: external_exports.object({
|
|
23699
|
+
tool: external_exports.string().optional(),
|
|
23700
|
+
command: external_exports.string().optional(),
|
|
23701
|
+
queries: external_exports.object({ path: external_exports.string().optional() }).passthrough().optional()
|
|
23702
|
+
}).passthrough().optional()
|
|
23703
|
+
}).passthrough();
|
|
23704
|
+
var adapterResultSchema = external_exports.object({
|
|
23705
|
+
final_answer: external_exports.string(),
|
|
23706
|
+
tool_calls: external_exports.array(adapterCallSchema).default([]),
|
|
23707
|
+
usage: external_exports.object({
|
|
23708
|
+
input_tokens: external_exports.number().nullable().optional(),
|
|
23709
|
+
output_tokens: external_exports.number().nullable().optional()
|
|
23710
|
+
}).passthrough().default({}),
|
|
23711
|
+
model_metadata: external_exports.object({
|
|
23712
|
+
context_compactions: external_exports.number().int().nonnegative().optional(),
|
|
23713
|
+
runtime_config: external_exports.record(external_exports.string(), external_exports.unknown()).optional(),
|
|
23714
|
+
context_management: external_exports.object({
|
|
23715
|
+
token_counting: external_exports.enum(["configured_estimate", "model_calibrated_estimate"]),
|
|
23716
|
+
estimate_scale: external_exports.number().positive(),
|
|
23717
|
+
calibration_samples: external_exports.number().int().nonnegative(),
|
|
23718
|
+
pinned_overflow: external_exports.enum(["error", "clip"]),
|
|
23719
|
+
compactions: external_exports.number().int().nonnegative()
|
|
23720
|
+
}).passthrough().optional()
|
|
23721
|
+
}).passthrough().optional()
|
|
23722
|
+
});
|
|
23723
|
+
function parseAdapterResult(raw) {
|
|
23724
|
+
return adapterResultSchema.parse(JSON.parse(raw));
|
|
23725
|
+
}
|
|
23726
|
+
|
|
23727
|
+
// src/tools/delegate-research.ts
|
|
23728
|
+
var researchResultSchema = external_exports.object({
|
|
23729
|
+
answer: external_exports.string(),
|
|
23730
|
+
summary: external_exports.string(),
|
|
23731
|
+
citations: external_exports.unknown().optional(),
|
|
23732
|
+
verification: external_exports.unknown(),
|
|
23733
|
+
usage: external_exports.unknown().optional(),
|
|
23734
|
+
telemetry: external_exports.unknown().optional()
|
|
23735
|
+
}).passthrough();
|
|
23736
|
+
async function routedResearchModel(endpoint) {
|
|
23737
|
+
const decision = JSON.parse(
|
|
23738
|
+
await route2(endpoint, "coding", "balanced", void 0, void 0, void 0, [], void 0, void 0, void 0)
|
|
23739
|
+
);
|
|
23740
|
+
if (typeof decision.selected_model !== "string" || !decision.selected_model) {
|
|
23741
|
+
throw new Error(
|
|
23742
|
+
"No research model: pass `model`, set FREELLAMA_MCP_DEFAULT_MODEL, or install a coding model (models{view:'installed'} shows what routing can use)."
|
|
23743
|
+
);
|
|
23744
|
+
}
|
|
23745
|
+
return decision.selected_model;
|
|
23746
|
+
}
|
|
23747
|
+
function registerDelegateResearchTool(server2) {
|
|
23748
|
+
server2.registerTool(
|
|
23749
|
+
"delegate_research",
|
|
23750
|
+
{
|
|
23751
|
+
description: "Use when: a bounded read-only question about files under workspacePath (where is X, what calls Y). Do not use when: editing, external facts, or final judgment. Returns: citations/answer; discard verification.recommendation=escalate, otherwise verify citations.",
|
|
23752
|
+
inputSchema: external_exports.object({
|
|
23753
|
+
question: external_exports.string().min(1),
|
|
23754
|
+
workspacePath: external_exports.string().min(1),
|
|
23755
|
+
adapter: external_exports.enum(["bash", "octocode"]).optional(),
|
|
23756
|
+
model: external_exports.string().min(1).optional(),
|
|
23757
|
+
endpoint: endpointParam,
|
|
23758
|
+
agent: external_exports.object({
|
|
23759
|
+
maxTurns: external_exports.number().int().positive().optional(),
|
|
23760
|
+
contextTokens: external_exports.number().int().positive().optional(),
|
|
23761
|
+
outputTokens: external_exports.number().int().positive().optional()
|
|
23762
|
+
}).strict().optional()
|
|
23763
|
+
}).strict(),
|
|
23764
|
+
outputSchema: researchResultSchema,
|
|
23765
|
+
annotations: { destructiveHint: false }
|
|
23766
|
+
},
|
|
23767
|
+
async ({ question, workspacePath, adapter, model, endpoint, agent }, extra) => {
|
|
23768
|
+
const chosenAdapter = adapter ?? DEFAULT_RESEARCH_ADAPTER;
|
|
23769
|
+
let resolvedWorkspace;
|
|
23770
|
+
let chosenModel;
|
|
23771
|
+
try {
|
|
23772
|
+
resolvedWorkspace = await assertAllowedWorkspace(workspacePath);
|
|
23773
|
+
chosenModel = model ?? DEFAULT_DELEGATE_MODEL ?? await routedResearchModel(endpoint);
|
|
23774
|
+
} catch (error2) {
|
|
23775
|
+
return researchErrorResult(error2);
|
|
23776
|
+
}
|
|
23777
|
+
const known = MODEL_EVIDENCE[chosenModel];
|
|
23778
|
+
if (known?.grade === "unusable") {
|
|
23779
|
+
return structuredResult({
|
|
23780
|
+
adapter: chosenAdapter,
|
|
23781
|
+
verification: assessDelegatedAnswer(question, 0, chosenModel),
|
|
23782
|
+
answer: "",
|
|
23783
|
+
toolCallCount: 0,
|
|
23784
|
+
usage: { inputTokens: null, outputTokens: null },
|
|
23785
|
+
telemetry: costTelemetry({ inputTokens: null, outputTokens: null }, EXTERNAL_COST),
|
|
23786
|
+
evidence: [],
|
|
23787
|
+
summary: `Refused before running: ${chosenModel} is measured unusable for research here (${known.note}). ${STRONG_MODEL_HINT}, or answer it yourself.`
|
|
23788
|
+
});
|
|
23789
|
+
}
|
|
23790
|
+
const dir = await mkdtemp(path5.join(tmpdir(), "freellama-delegate-"));
|
|
23791
|
+
const promptFile = path5.join(dir, "prompt.md");
|
|
23792
|
+
const resultFile = path5.join(dir, "result.json");
|
|
23793
|
+
try {
|
|
23794
|
+
await writeFile(promptFile, `${question}
|
|
23795
|
+
`, "utf8");
|
|
23796
|
+
const adapter2 = RESEARCH_ADAPTERS[chosenAdapter];
|
|
23797
|
+
if (!existsSync5(adapter2)) {
|
|
23798
|
+
return researchErrorResult(
|
|
23799
|
+
new Error(
|
|
23800
|
+
`research adapter not found at ${adapter2}. It is compiled to <package>/dist/research by the package build. Reinstall, or run \`yarn build\` from packages/mcp/ to rebuild it.`
|
|
23801
|
+
)
|
|
23802
|
+
);
|
|
23803
|
+
}
|
|
23804
|
+
const running = runDelegate(process.execPath, [adapter2], {
|
|
23805
|
+
signal: extra.signal,
|
|
23806
|
+
env: {
|
|
23807
|
+
...delegateEnvironment(),
|
|
23808
|
+
FREELLAMA_TARGET_MODEL: chosenModel,
|
|
23809
|
+
FREELLAMA_AGENT_MANAGED_ENDPOINT: endpoint ?? DEFAULT_SERVE_ENDPOINT,
|
|
23810
|
+
FREELLAMA_AGENT_TOKEN_CALIBRATION_DIR: DEFAULT_TOKEN_CALIBRATION_DIR,
|
|
23811
|
+
FREELLAMA_BENCH_WORKSPACE: resolvedWorkspace,
|
|
23812
|
+
FREELLAMA_BENCH_PROMPT: promptFile,
|
|
23813
|
+
FREELLAMA_AGENT_RESULT: resultFile,
|
|
23814
|
+
FREELLAMA_AGENT_MAX_TURNS: String(agent?.maxTurns ?? DEFAULT_DELEGATE_MAX_TURNS),
|
|
23815
|
+
...agent?.contextTokens !== void 0 ? { FREELLAMA_AGENT_NUM_CTX: String(agent.contextTokens) } : {},
|
|
23816
|
+
...agent?.outputTokens !== void 0 ? { FREELLAMA_AGENT_NUM_PREDICT: String(agent.outputTokens) } : {}
|
|
23817
|
+
},
|
|
23818
|
+
timeoutMs: DEFAULT_DELEGATE_TIMEOUT_SECONDS * 1e3,
|
|
23819
|
+
// The answer is read from `resultFile`, never from stdout — but execFile's usual 1 MB
|
|
23820
|
+
// maxBuffer still applies to the agent's own progress logging, and overflowing it kills
|
|
23821
|
+
// the subprocess and surfaces as a research failure with no explanation. Give the logs
|
|
23822
|
+
// room; nothing here is proportional to the size of the answer.
|
|
23823
|
+
maxBuffer: 32 * 1024 * 1024
|
|
23824
|
+
// On timeout, abort, or overflow runDelegate SIGKILLs the whole process group: SIGTERM can
|
|
23825
|
+
// be swallowed by an adapter blocked in a long model call, and killing the adapter alone
|
|
23826
|
+
// would orphan `npx octocode` and sandbox shells.
|
|
23827
|
+
});
|
|
23828
|
+
liveDelegates.add(running.child);
|
|
23829
|
+
let adapterError = null;
|
|
23830
|
+
try {
|
|
23831
|
+
await running.done;
|
|
23832
|
+
} catch (error2) {
|
|
23833
|
+
adapterError = error2;
|
|
23834
|
+
} finally {
|
|
23835
|
+
liveDelegates.delete(running.child);
|
|
23836
|
+
killDelegateTree(running.child);
|
|
23837
|
+
}
|
|
23838
|
+
let result;
|
|
23839
|
+
try {
|
|
23840
|
+
result = parseAdapterResult(await readFile2(resultFile, "utf8"));
|
|
23841
|
+
} catch {
|
|
23842
|
+
return researchErrorResult(
|
|
23843
|
+
adapterError ?? new Error(
|
|
23844
|
+
`research adapter exited without writing a readable result file \u2014 it was killed before it could report. Check that the model is loadable and that ${DEFAULT_DELEGATE_TIMEOUT_SECONDS}s is enough for this question.`
|
|
23845
|
+
)
|
|
23846
|
+
);
|
|
23847
|
+
}
|
|
23848
|
+
const evidence = result.tool_calls.map((call, index) => {
|
|
23849
|
+
const target = call.arguments?.queries?.path;
|
|
23850
|
+
const rawDetail = call.arguments?.command ?? null;
|
|
23851
|
+
const detail = rawDetail ? clipText(rawDetail, 400) : null;
|
|
23852
|
+
return {
|
|
23853
|
+
step: index + 1,
|
|
23854
|
+
tool: call.arguments?.tool ?? call.raw_name ?? "?",
|
|
23855
|
+
// The adapters record "ok" | "error" | "repeat" per call. Carrying it through is what
|
|
23856
|
+
// lets a reader tell a run that read three files from one that failed three commands —
|
|
23857
|
+
// indistinguishable in the trail before, and graded identically.
|
|
23858
|
+
status: call.status ?? "ok",
|
|
23859
|
+
path: target ? path5.relative(resolvedWorkspace, target) : detail ? extractExistingWorkspacePath(detail, resolvedWorkspace) : null,
|
|
23860
|
+
detail,
|
|
23861
|
+
detail_truncated: rawDetail !== detail
|
|
23862
|
+
};
|
|
23863
|
+
});
|
|
23864
|
+
const succeeded = evidence.filter((step) => step.status === "ok");
|
|
23865
|
+
const failed = evidence.length - succeeded.length;
|
|
23866
|
+
const evidenceText = evidence.map(
|
|
23867
|
+
(step) => ` ${step.step}. ${step.tool}${step.status === "ok" ? "" : ` [${step.status}]`}${step.path ? ` -> ${step.path}` : ""}${step.detail ? `: ${step.detail}` : ""}`
|
|
23868
|
+
).join("\n");
|
|
23869
|
+
const citations = succeeded.map((step) => ({ step: step.step, tool: step.tool, path: step.path }));
|
|
23870
|
+
if (adapterError) {
|
|
23871
|
+
const diagnostic = `research adapter failed: ${result.final_answer}` + (evidenceText ? `
|
|
23872
|
+
Evidence collected before the failure:
|
|
23873
|
+
${evidenceText}` : "");
|
|
23874
|
+
const refusal = external_exports.object({ receipt: external_exports.object({ error: external_exports.string() }).passthrough() }).safeParse(result.model_metadata?.transport_error);
|
|
23875
|
+
if (refusal.success) {
|
|
23876
|
+
return researchErrorResult(new Error(JSON.stringify(refusal.data.receipt)), diagnostic);
|
|
23877
|
+
}
|
|
23878
|
+
return researchErrorResult(new Error(diagnostic));
|
|
23879
|
+
}
|
|
23880
|
+
const verification = assessDelegatedAnswer(question, succeeded.length, chosenModel);
|
|
23881
|
+
const summary = `Delegated answer ready: ${result.tool_calls.length} tool call(s)` + (failed > 0 ? `, ${failed} of which did not succeed` : "") + `; ${result.usage.input_tokens ?? "?"} input / ${result.usage.output_tokens ?? "?"} output local tokens; verification=${verification.recommendation}. Read structuredContent.answer and citations.`;
|
|
23882
|
+
const payload = {
|
|
23883
|
+
adapter: chosenAdapter,
|
|
23884
|
+
verification,
|
|
23885
|
+
answer: result.final_answer,
|
|
23886
|
+
// recommendation + why live under `verification`; `citations` completes the triple so a
|
|
23887
|
+
// caller never has to read `summary` to act on the result.
|
|
23888
|
+
citations,
|
|
23889
|
+
toolCallCount: result.tool_calls.length,
|
|
23890
|
+
successfulToolCallCount: succeeded.length,
|
|
23891
|
+
usage: {
|
|
23892
|
+
inputTokens: result.usage.input_tokens,
|
|
23893
|
+
outputTokens: result.usage.output_tokens
|
|
23894
|
+
},
|
|
23895
|
+
telemetry: costTelemetry({
|
|
23896
|
+
inputTokens: result.usage.input_tokens ?? null,
|
|
23897
|
+
outputTokens: result.usage.output_tokens ?? null
|
|
23898
|
+
}, EXTERNAL_COST),
|
|
23899
|
+
contextManagement: result.model_metadata?.context_management ?? null,
|
|
23900
|
+
execution: { receipts: result.model_metadata?.execution_receipts ?? [] },
|
|
23901
|
+
evidence,
|
|
23902
|
+
summary
|
|
23903
|
+
};
|
|
23904
|
+
const cited = [...new Set(citations.map((citation) => citation.path).filter(Boolean))];
|
|
23905
|
+
const answerText = `${clipText(result.final_answer, ANSWER_TEXT_MAX_CHARS)}
|
|
23906
|
+
|
|
23907
|
+
verification: ${verification.recommendation}` + (cited.length ? `
|
|
23908
|
+
cited: ${cited.slice(0, 20).join(", ")}` : "") + `
|
|
23909
|
+
(${chosenModel}, ${result.tool_calls.length} tool call(s))`;
|
|
23910
|
+
return structuredResult(payload, { text: answerText });
|
|
23911
|
+
} catch (error2) {
|
|
23912
|
+
return researchErrorResult(error2);
|
|
23913
|
+
} finally {
|
|
23914
|
+
await rm(dir, { recursive: true, force: true });
|
|
23915
|
+
}
|
|
23916
|
+
}
|
|
23917
|
+
);
|
|
23918
|
+
}
|
|
23919
|
+
|
|
23920
|
+
// src/index.ts
|
|
23921
|
+
installProcessLifecycle();
|
|
23922
|
+
var INSTRUCTIONS = `models{view:"installed"}; models{view:"resident"}.
|
|
23923
|
+
run_task preview never executes; code_review aliases coding. requiredCapabilities:["tools"] previews a tool route; omit preview and supply the payload. Caller runs tool calls.
|
|
23924
|
+
quality objective needs a task policy or explicit model; medium confidence needs policy+benchmark; observed placement needs /api/ps proof.
|
|
23925
|
+
Scoped run_task needs scopeId+scopeRevision. defer:true: job.id\u2192task_jobs.jobId unchanged. Restart loses scopes/sessions/jobs.
|
|
23926
|
+
Check isError first; prefer structuredContent, else content[].text. page.next_cursor\u2192cursor unchanged.
|
|
23927
|
+
caller owns task decomposition/prompts/format/verification; findings are candidates, not accepted defects. operator owns endpoints, exact --cpu-model assignments/lifecycle. Ollama plus the OS/driver run physical CPU/GPU.
|
|
23928
|
+
ask approval for one exact tag and reported size before ollama_manage pull; search or recommendation is never download permission. Stop/delete need exact-tag approval.
|
|
23929
|
+
serve :11435; default-endpoint autostart optional. Docs: freellama://docs/index.`;
|
|
23930
|
+
var server = new McpServer(
|
|
23931
|
+
{ name: "freellama", version: SERVER_VERSION },
|
|
23932
|
+
{ instructions: INSTRUCTIONS }
|
|
22969
23933
|
);
|
|
22970
|
-
|
|
23934
|
+
registerDocsResources(server);
|
|
23935
|
+
registerDoctorTool(server);
|
|
23936
|
+
registerSessionTool(server);
|
|
23937
|
+
registerModelsTool(server);
|
|
23938
|
+
registerScopeTool(server);
|
|
23939
|
+
registerRunTaskTool(server);
|
|
23940
|
+
registerTaskJobsTool(server);
|
|
23941
|
+
registerOllamaLifecycleTools(server);
|
|
23942
|
+
registerDelegateResearchTool(server);
|
|
23943
|
+
var transport = withoutSchemaDialect(new StdioServerTransport());
|
|
22971
23944
|
await server.connect(transport);
|