@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/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(path5) {
3265
- let input = path5;
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(String(component.port));
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 path5 = queryIndex === -1 ? wsComponent.resourceName : wsComponent.resourceName.slice(0, queryIndex);
3671
- wsComponent.path = path5 && path5 !== "/" ? path5 : void 0;
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(serialize2(uri, options), options);
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 serialize2(resolved, schemelessOptions);
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(serialize2(base, options), options);
3876
- relative = parse3(serialize2(relative, options), options);
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 serialize2(cmpts, opts) {
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[0] === "[" && host[host.length - 1] === "]") && hasMalformedPercentEncoding(host) || hasMalformedPercentEncoding(matches[6]) || hasMalformedPercentEncoding(matches[7]) || hasMalformedPercentEncoding(matches[8]);
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[0] !== "[" && (options.domainHost || schemeHandler && schemeHandler.domainHost) && isIP === false && nonSimpleDomain(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[0] === "[" && parsed.host[parsed.host.length - 1] === "]";
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 = bracketedIPLiteral && ipv6result.error === true;
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
- malformedHost = canonicalizeHost(parsed, options, schemeHandler, isIP);
4140
- if (!schemeHandler || schemeHandler && !schemeHandler.skipNormalize) {
4141
- if (uri.indexOf("%") !== -1) {
4142
- if (parsed.host !== void 0 && !malformedIPLiteral) {
4143
- const host = isIP ? parsed.host : normalizePercentEncoding(parsed.host, true);
4144
- parsed.host = reescapeHostDelimiters(host, isIP);
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 : serialize2(parsed, opts),
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 : serialize2(uri, opts);
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: serialize2,
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: path5, errorMaps, issueData } = params;
7679
- const fullPath = [...path5, ...issueData.path || []];
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 errorMessage = "";
7693
+ let errorMessage2 = "";
7692
7694
  const maps = errorMaps.filter((m) => !!m).slice().reverse();
7693
7695
  for (const map of maps) {
7694
- errorMessage = map(fullIssue, { data, defaultError: errorMessage }).message;
7696
+ errorMessage2 = map(fullIssue, { data, defaultError: errorMessage2 }).message;
7695
7697
  }
7696
7698
  return {
7697
7699
  ...issueData,
7698
7700
  path: fullPath,
7699
- message: errorMessage
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(status, results) {
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
- status.dirty();
7742
+ status2.dirty();
7741
7743
  arrayValue.push(s.value);
7742
7744
  }
7743
- return { status: status.value, value: arrayValue };
7745
+ return { status: status2.value, value: arrayValue };
7744
7746
  }
7745
- static async mergeObjectAsync(status, pairs) {
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(status, syncPairs);
7757
+ return _ParseStatus.mergeObjectSync(status2, syncPairs);
7756
7758
  }
7757
- static mergeObjectSync(status, pairs) {
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
- status.dirty();
7768
+ status2.dirty();
7767
7769
  if (value.status === "dirty")
7768
- status.dirty();
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: status.value, value: finalObject };
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, path5, key) {
7797
+ constructor(parent, value, path6, key) {
7796
7798
  this._cachedPath = [];
7797
7799
  this.parent = parent;
7798
7800
  this.data = value;
7799
- this._path = path5;
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 status = new ParseStatus();
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
- status.dirty();
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
- status.dirty();
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
- status.dirty();
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
- status.dirty();
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
- status.dirty();
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
- status.dirty();
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
- status.dirty();
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
- status.dirty();
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
- status.dirty();
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
- status.dirty();
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
- status.dirty();
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
- status.dirty();
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
- status.dirty();
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
- status.dirty();
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
- status.dirty();
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
- status.dirty();
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
- status.dirty();
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
- status.dirty();
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
- status.dirty();
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
- status.dirty();
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
- status.dirty();
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
- status.dirty();
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
- status.dirty();
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
- status.dirty();
8514
+ status2.dirty();
8513
8515
  }
8514
8516
  } else {
8515
8517
  util.assertNever(check2);
8516
8518
  }
8517
8519
  }
8518
- return { status: status.value, value: input.data };
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 status = new ParseStatus();
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
- status.dirty();
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
- status.dirty();
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
- status.dirty();
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
- status.dirty();
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
- status.dirty();
8854
+ status2.dirty();
8853
8855
  }
8854
8856
  } else {
8855
8857
  util.assertNever(check2);
8856
8858
  }
8857
8859
  }
8858
- return { status: status.value, value: input.data };
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 status = new ParseStatus();
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
- status.dirty();
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
- status.dirty();
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
- status.dirty();
9061
+ status2.dirty();
9060
9062
  }
9061
9063
  } else {
9062
9064
  util.assertNever(check2);
9063
9065
  }
9064
9066
  }
9065
- return { status: status.value, value: input.data };
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 status = new ParseStatus();
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
- status.dirty();
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
- status.dirty();
9252
+ status2.dirty();
9251
9253
  }
9252
9254
  } else {
9253
9255
  util.assertNever(check2);
9254
9256
  }
9255
9257
  }
9256
9258
  return {
9257
- status: status.value,
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
- status.dirty();
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
- status.dirty();
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
- status.dirty();
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(status, result2);
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(status, result);
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
- status.dirty();
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(status, syncPairs);
9665
+ return ParseStatus.mergeObjectSync(status2, syncPairs);
9664
9666
  });
9665
9667
  } else {
9666
- return ParseStatus.mergeObjectSync(status, pairs);
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
- status.dirty();
10159
+ status2.dirty();
10158
10160
  }
10159
- return { status: status.value, value: merged.data };
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
- status.dirty();
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(status, results);
10237
+ return ParseStatus.mergeArray(status2, results);
10236
10238
  });
10237
10239
  } else {
10238
- return ParseStatus.mergeArray(status, items);
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(status, pairs);
10292
+ return ParseStatus.mergeObjectAsync(status2, pairs);
10291
10293
  } else {
10292
- return ParseStatus.mergeObjectSync(status, pairs);
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
- status.dirty();
10352
+ status2.dirty();
10351
10353
  }
10352
10354
  finalMap.set(key.value, value.value);
10353
10355
  }
10354
- return { status: status.value, value: finalMap };
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
- status.dirty();
10367
+ status2.dirty();
10366
10368
  }
10367
10369
  finalMap.set(key.value, value.value);
10368
10370
  }
10369
- return { status: status.value, value: finalMap };
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
- status.dirty();
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
- status.dirty();
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
- status.dirty();
10428
+ status2.dirty();
10427
10429
  parsedSet.add(element.value);
10428
10430
  }
10429
- return { status: status.value, value: parsedSet };
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
- status.abort();
10768
+ status2.abort();
10767
10769
  } else {
10768
- status.dirty();
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 (status.value === "aborted")
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 (status.value === "dirty")
10793
+ if (status2.value === "dirty")
10792
10794
  return DIRTY(result.value);
10793
10795
  return result;
10794
10796
  });
10795
10797
  } else {
10796
- if (status.value === "aborted")
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 (status.value === "dirty")
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
- status.dirty();
10834
+ status2.dirty();
10833
10835
  executeRefinement(inner.value);
10834
- return { status: status.value, value: inner.value };
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
- status.dirty();
10842
+ status2.dirty();
10841
10843
  return executeRefinement(inner.value).then(() => {
10842
- return { status: status.value, value: inner.value };
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: status.value, value: result };
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: status.value,
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
- status.dirty();
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
- status.dirty();
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, path5) {
11437
- if (!path5)
11438
+ function getElementAtPath(obj, path6) {
11439
+ if (!path6)
11438
11440
  return obj;
11439
- return path5.reduce((acc, key) => acc?.[key], obj);
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(path5, issues) {
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(path5);
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(path5) {
15175
- if (path5.length === 0) {
15176
+ function getDotPath(path6) {
15177
+ if (path6.length === 0) {
15176
15178
  return "object root";
15177
15179
  }
15178
- return path5.reduce((acc, seg, index) => {
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(status) {
17478
- return status === "completed" || status === "failed" || status === "cancelled";
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, errorMessage, refs) {
17539
+ function addErrorMessage(res, key, errorMessage2, refs) {
17538
17540
  if (!refs?.errorMessages)
17539
17541
  return;
17540
- if (errorMessage) {
17542
+ if (errorMessage2) {
17541
17543
  res.errorMessage = {
17542
17544
  ...res.errorMessage,
17543
- [key]: errorMessage
17545
+ [key]: errorMessage2
17544
17546
  };
17545
17547
  }
17546
17548
  }
17547
- function setResponseValueAndErrors(res, key, value, errorMessage, refs) {
17549
+ function setResponseValueAndErrors(res, key, value, errorMessage2, refs) {
17548
17550
  res[key] = value;
17549
- addErrorMessage(res, key, errorMessage, refs);
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 errorMessage = message;
18861
- const error2 = new McpError(errorMessage.error.code, errorMessage.error.message, errorMessage.error.data);
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, status, result) => {
19698
- await taskStore.storeTaskResult(taskId, status, result, sessionId);
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, status, statusMessage) => {
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 "${status}". Terminal states (completed, failed, cancelled) cannot transition to other states.`);
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, status, statusMessage, sessionId);
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 errorMessage = validatedRequest.error instanceof Error ? validatedRequest.error.message : String(validatedRequest.error);
20153
- throw new McpError(ErrorCode.InvalidParams, `Invalid tools/call request: ${errorMessage}`);
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 errorMessage = taskValidationResult.error instanceof Error ? taskValidationResult.error.message : String(taskValidationResult.error);
20161
- throw new McpError(ErrorCode.InvalidParams, `Invalid task creation result: ${errorMessage}`);
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 errorMessage = validationResult.error instanceof Error ? validationResult.error.message : String(validationResult.error);
20168
- throw new McpError(ErrorCode.InvalidParams, `Invalid tools/call result: ${errorMessage}`);
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(errorMessage) {
20664
+ createToolError(errorMessage2) {
20663
20665
  return {
20664
20666
  content: [
20665
20667
  {
20666
20668
  type: "text",
20667
- text: errorMessage
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 errorMessage = getParseErrorMessage(error2);
20686
- throw new McpError(ErrorCode.InvalidParams, `Input validation error: Invalid arguments for tool ${toolName}: ${errorMessage}`);
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 errorMessage = getParseErrorMessage(error2);
20711
- throw new McpError(ErrorCode.InvalidParams, `Output validation error: Invalid structured content for tool ${toolName}: ${errorMessage}`);
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 errorMessage = getParseErrorMessage(error2);
20924
- throw new McpError(ErrorCode.InvalidParams, `Invalid arguments for prompt ${request.params.name}: ${errorMessage}`);
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 REPO_ROOT = findRepoRoot() ?? path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
21451
- var PACKAGE_ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
21469
+ var CHECKOUT_ROOT = findRepoRoot();
21470
+ var REPO_ROOT = CHECKOUT_ROOT ?? PACKAGE_ROOT;
21452
21471
  function adapterPath(file) {
21453
- const bundled = path.join(PACKAGE_ROOT, "adapters", file);
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("bash_agent.py"),
21459
- octocode: adapterPath("octocode_agent.py")
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 ?? "qwen3.8:27b-mlx";
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 ?? findRepoRoot() ?? ""
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/native.ts
21526
- import { createRequire } from "node:module";
21527
- import native from "../native/index.js";
21528
- var { doctor, machine, health, createSession, deleteSession, listModels, route, runTask, runTaskRequest, runTaskBatchRequest } = native;
21529
- var { version: SERVER_VERSION } = createRequire(import.meta.url)("../package.json");
21530
-
21531
- // src/helpers.ts
21532
- import { existsSync as existsSync2, realpathSync, statSync } from "node:fs";
21533
- import path2 from "node:path";
21534
- function extractExistingWorkspacePath(command, workspace) {
21535
- let root;
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
- root = realpathSync(workspace);
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
- const tokens = command.match(/(?:[^\s"'\\]+|"(?:\\.|[^"])*"|'[^']*')+/g) ?? [];
21542
- const patternFlags = /* @__PURE__ */ new Set(["-name", "-iname", "-path", "-ipath", "-regex", "-iregex", "-wholename"]);
21543
- for (let index = 0; index < tokens.length; index += 1) {
21544
- const raw = tokens[index] ?? "";
21545
- if (index > 0 && patternFlags.has(tokens[index - 1] ?? "")) continue;
21546
- const token = raw.replace(/^["']|["']$/g, "").replace(/[|;&)]+$/g, "").replace(/:\d+(?::\d+)?$/, "");
21547
- if (!token || token.startsWith("-") || /[*?$`]/.test(token)) continue;
21548
- const candidate = path2.isAbsolute(token) ? token : path2.resolve(root, token);
21549
- if (!existsSync2(candidate)) continue;
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 resolved = realpathSync(candidate);
21552
- const relative = path2.relative(root, resolved);
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
- return null;
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
- async function ollamaFetch(endpoint, path5, init = {}) {
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}${path5}`, {
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: controller.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"} ${path5} -> HTTP ${response.status}: ${text}`);
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 summarizeOllamaPullStream(text) {
21586
- const events = [];
21587
- for (const line of text.split(/\r?\n/)) {
21588
- const trimmed = line.trim();
21589
- if (!trimmed) continue;
21590
- try {
21591
- const parsed = JSON.parse(trimmed);
21592
- if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
21593
- events.push(parsed);
21594
- }
21595
- } catch {
21596
- }
21597
- }
21598
- if (events.length === 0) {
21599
- if (!text.trim()) return { status: "empty" };
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
- const last = events[events.length - 1] ?? {};
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().describe("serve endpoint, default :11435");
21640
- var ollamaEndpointParam = external_exports.string().min(1).optional().describe("Ollama endpoint, default :11434");
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("code_review=coding.");
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().describe('"balanced"/"quality" need a policy; "fastest" does not');
21657
- var executionPreferenceParam = external_exports.enum(["auto", "prefer_cpu", "prefer_gpu"]).optional().describe(
21658
- 'Backend hint. "auto" uses guarded runtime feedback; prefer_* falls back when no eligible operator-assigned model exists.'
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().describe('Additional hard requirements, e.g. ["vision"] or ["tools"]. Fails closed if unmet.');
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: taskParam,
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
- messages: external_exports.array(external_exports.object({ role: external_exports.enum(["system", "user", "assistant", "tool"]), content: external_exports.string() }).passthrough()).min(1).optional(),
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: external_exports.array(external_exports.record(external_exports.unknown())).min(1).optional(),
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(usage, externalCost) {
21995
+ function costTelemetry(usage2, externalCost) {
21767
21996
  const local = {
21768
- inputTokens: usage.inputTokens,
21769
- outputTokens: usage.outputTokens,
21770
- totalTokens: usage.inputTokens === null || usage.outputTokens === null ? null : usage.inputTokens + usage.outputTokens,
21771
- totalDurationNs: usage.totalDurationNs ?? null
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 || usage.inputTokens === null || usage.outputTokens === null) {
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 = usage.inputTokens * externalCost.input / 1e6;
21781
- const outputUsd = usage.outputTokens * externalCost.output / 1e6;
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: options.legacyJson ? serialize(value) : resultSummary(value) }],
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
- const managedServeUnavailable = /(?:connection refused|connect error|failed to connect|error sending request)/i.test(message) && /(?:_freellama|127\.0\.0\.1:11435|localhost:11435)/i.test(message);
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
- return { content: [{ type: "text", text: actionableMessage }], isError: true };
21849
- }
21850
- var adapterCallSchema = external_exports.object({
21851
- raw_name: external_exports.string().optional(),
21852
- status: external_exports.string().optional(),
21853
- arguments: external_exports.object({
21854
- tool: external_exports.string().optional(),
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 name = block.match(/href="\/library\/([^"]+)"/)?.[1];
22377
+ const link = block.match(/href="\/(?:library\/([\w.-]+)|([\w.-]+\/[\w.-]+))"/);
22378
+ const name = link?.[1] ?? link?.[2];
21907
22379
  if (!name) continue;
21908
- const description = block.match(/<p class="max-w-lg[^"]*">([\s\S]*?)<\/p>/)?.[1]?.replace(/<[^>]+>/g, "").replace(/&#39;/g, "'").replace(/&amp;/g, "&").replace(/&quot;/g, '"').replace(/\s+/g, " ").trim() ?? "";
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
- cloudOnly: capabilities.includes("cloud")
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 href="\/library\/([^"]+)" class="sm:hidden[\s\S]*?<p class="flex text-neutral-500">([^<]*)<\/p>/g;
22403
+ const row = /<a\b([^>]*)>([\s\S]*?)<\/a>/gi;
21930
22404
  for (const m of html.matchAll(row)) {
21931
- const tag = m[1];
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 = m[2].replace(/&middot;/g, "\xB7").split("\xB7").map((x) => x.trim());
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/delegate.ts
21950
- import { existsSync as existsSync3, readFileSync } from "node:fs";
21951
- import path3 from "node:path";
21952
- var MODEL_EVIDENCE = (() => {
21953
- const configured = process.env.FREELLAMA_MCP_MODEL_EVIDENCE;
21954
- const candidates = [
21955
- configured,
21956
- path3.join(REPO_ROOT, "benchmark/evidence/model-evidence.json")
21957
- ].filter((x) => Boolean(x));
21958
- for (const file of candidates) {
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
- if (!existsSync3(file)) continue;
21961
- const parsed = JSON.parse(readFileSync(file, "utf8"));
21962
- return parsed.models ?? {};
21963
- } catch {
22496
+ return await work;
22497
+ } finally {
22498
+ this.pending.delete(ref.family);
21964
22499
  }
21965
22500
  }
21966
- return {};
21967
- })();
21968
- function assessDelegatedAnswer(question, evidenceCount, model) {
21969
- const grounded = evidenceCount > 0;
21970
- const evidence = MODEL_EVIDENCE[model];
21971
- if (evidence?.grade === "unusable") {
21972
- return {
21973
- recommendation: "escalate",
21974
- grounded,
21975
- 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. Re-run with a ~27B model (see README), or answer it yourself.`,
21976
- measuredBaseRate: `${model}: ${evidence.note}`
21977
- };
21978
- }
21979
- if (!evidence) {
21980
- return {
21981
- recommendation: "verify",
21982
- grounded,
21983
- 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.`,
21984
- measuredBaseRate: "no measured base rate for this model"
21985
- };
21986
- }
21987
- if (evidence.grade === "weak" && grounded) {
21988
- return {
21989
- recommendation: "verify",
21990
- grounded,
21991
- why: `${model} holds up on simple single-file lookups but not beyond (${evidence.note}). Check the evidence trail, or re-run on a ~27B model (see README) if the answer matters.`,
21992
- measuredBaseRate: `${model}: ${evidence.note}`
21993
- };
21994
- }
21995
- if (!grounded) {
21996
- return {
21997
- recommendation: "escalate",
21998
- grounded: false,
21999
- 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.",
22000
- measuredBaseRate: "ungrounded answers have no measured accuracy \u2014 they were never the tested path"
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
- if (evidenceCount > 5) {
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
- recommendation: "verify",
22015
- grounded: true,
22016
- 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.`,
22017
- measuredBaseRate: `${model}: 98.9% on grounded lookups, measured within a 1-5 file scope`
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
- recommendation: "accept",
22022
- grounded: true,
22023
- 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}).`,
22024
- measuredBaseRate: `${model}: 98.9% on grounded lookups (100+ questions)`
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
- // src/index.ts
22029
- var execFileAsync = promisify(execFile);
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 withBatchTelemetry(result) {
22045
- if (!("structuredContent" in result)) return result;
22046
- const payload = result.structuredContent;
22047
- const rows = Array.isArray(payload.results) ? payload.results : [];
22048
- let inputTokens = 0;
22049
- let outputTokens = 0;
22050
- let complete = 0;
22051
- const results = rows.map((row) => {
22052
- if (!row || typeof row !== "object") return row;
22053
- const item = row;
22054
- if (item.ok !== true || !item.response || typeof item.response !== "object") return item;
22055
- const response = item.response;
22056
- const metrics = response.metrics;
22057
- const input = typeof metrics?.prompt_tokens === "number" ? metrics.prompt_tokens : null;
22058
- const output = typeof metrics?.output_tokens === "number" ? metrics.output_tokens : null;
22059
- if (input !== null && output !== null) {
22060
- inputTokens += input;
22061
- outputTokens += output;
22062
- complete += 1;
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
- return { ...item, response: { ...response, telemetry: costTelemetry({ inputTokens: input, outputTokens: output }, EXTERNAL_COST) } };
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
- function pageLiveList(items, limit, cursor, identity) {
22073
- const pageSize = limit ?? 20;
22074
- const fingerprint = createHash("sha256").update(items.map(identity).join("\n")).digest("base64url").slice(0, 16);
22075
- let offset = 0;
22076
- if (cursor) {
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 parsed = JSON.parse(Buffer.from(cursor, "base64url").toString("utf8"));
22079
- if (!Number.isInteger(parsed.offset) || parsed.offset < 0 || parsed.fingerprint !== fingerprint) {
22080
- throw new Error("invalid or stale cursor");
22081
- }
22082
- offset = parsed.offset;
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 family = model.split(":")[0];
22394
- const familyPage = await fetchPage(`https://ollama.com/library/${encodeURIComponent(family)}`);
22395
- const { tags } = parseModelTags(familyPage, family);
22396
- const { installed: installed2, memoryBytes } = await localState();
22397
- const budget = memoryBytes ? memoryBytes * 0.6 : null;
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 && !/cloud/.test(t.tag));
22405
- const embeddingMentions = (familyPage.match(/embedding/gi) ?? []).length;
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: `https://ollama.com/library/${encodeURIComponent(family)}`,
22413
- ...tags.length === 0 ? {
22414
- tagsUnavailable: `No pullable tags found for "${family}". Either it is cloud-only (no local download), the family name is wrong, or ollama.com changed its markup. Open the page above to check before concluding the model does not exist.`
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 url = `https://ollama.com/search?${params.toString()}`;
22439
- const parsed = parseModelSearch(await fetchPage(url)).slice(0, limit ?? 10);
22440
- const { installed } = await localState();
22441
- const installedFamilies = new Set([...installed].map((n) => n.split(":")[0]));
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
- server.registerTool(
22454
- "run_task",
22455
- {
22456
- description: "Use when: routing or executing supplied content. Do not use when: workspace files must be read; use delegate_research. Preview consequential work first; it never generates. Returns: decision or response with receipts. Next: inspect structured observation and verification.",
22457
- inputSchema: {
22458
- endpoint: endpointParam,
22459
- task: taskParam,
22460
- objective: objectiveParam,
22461
- model: external_exports.string().min(1).optional().describe("Force this exact installed model name."),
22462
- sessionId: external_exports.string().min(1).optional().describe("Session id for model affinity across calls."),
22463
- contextTokens: external_exports.number().int().positive().optional().describe("Total Ollama context window (num_ctx), including input and output."),
22464
- executionPreference: executionPreferenceParam,
22465
- minPlacementEvidence: minPlacementEvidenceParam,
22466
- requiredCapabilities: requiredCapabilitiesParam,
22467
- prompt: external_exports.string().min(1).optional().describe("Chat input when messages is omitted."),
22468
- images: external_exports.array(external_exports.string().min(1)).min(1).optional().describe("base64, no data-URI prefix; prompt mode only; requires an explicit tested vision model"),
22469
- messages: external_exports.array(
22470
- external_exports.object({ role: external_exports.enum(["system", "user", "assistant", "tool"]), content: external_exports.string() }).passthrough()
22471
- ).min(1).optional().describe(
22472
- "wins over prompt; preserves Ollama images, thinking, tool_calls, tool_name, and other message fields"
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
- outputSchema: taskResultSchema,
22490
- annotations: { destructiveHint: false }
22491
- },
22492
- async ({
22493
- endpoint,
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
- const effectiveRequiredCapabilities = tools === void 0 ? requiredCapabilities : withRequiredCapability(requiredCapabilities, "tools");
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
- if (!preview && task !== "embedding") {
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 (prompt === void 0 && (messages === void 0 || messages.length === 0)) {
22562
- return errorResult(new Error(`task "${task}" requires \`prompt\` or \`messages\`.`));
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 (images !== void 0 && messages !== void 0) {
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 (images !== void 0 && model === void 0) {
22570
- return errorResult(new Error("Images require an explicit tested vision-capable `model`."));
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
- if (preview) {
22574
- const result2 = parsedResult(
22575
- await route(endpoint, canonicalTask, objective, model, sessionId, contextTokens, effectiveRequiredCapabilities, minConfidence, executionPreference, minPlacementEvidence)
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
- const result = parsedResult(
22596
- await runTaskRequest(endpoint ?? DEFAULT_SERVE_ENDPOINT, {
22597
- task: canonicalTask,
22598
- objective: objective ?? "balanced",
22599
- model,
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
- if (!returnEmbeddings && "structuredContent" in result) {
22623
- const trimmed = summarizeEmbeddings(result.structuredContent);
22624
- if (trimmed) return withTaskTelemetry(structuredResult(trimmed));
22625
- }
22626
- return withTaskTelemetry(result);
22627
- } catch (error2) {
22628
- return errorResult(error2);
22629
- }
22630
- }
22631
- );
22632
- server.registerTool(
22633
- "run_task_batch",
22634
- {
22635
- description: "Use when: independent work. Do not use when: dependencies. Inputs: [{id, independent:true, task}]; maxParallelism caps dispatch. Returns: ordered receipts.",
22636
- inputSchema: {
22637
- tasks: external_exports.array(batchItemParam).min(1).max(64),
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
- } else if (task.input !== void 0 || task.prompt === void 0 && task.messages === void 0) {
22653
- return errorResult(new Error(`batch item ${item.id}: chat work requires prompt or messages and accepts no input.`));
22654
- }
22655
- if (task.images !== void 0 && task.model === void 0) {
22656
- return errorResult(new Error(`batch item ${item.id}: images require an explicit tested vision model.`));
22657
- }
22658
- }
22659
- return withBatchTelemetry(parsedResult(await runTaskBatchRequest(endpoint ?? DEFAULT_SERVE_ENDPOINT, {
22660
- tasks: tasks.map((item) => {
22661
- const task = item.task;
22662
- return {
22663
- id: item.id,
22664
- independent: item.independent,
22665
- task: {
22666
- task: task.task === "code_review" ? "coding" : task.task,
22667
- objective: task.objective ?? "balanced",
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
- max_parallelism: maxParallelism
22693
- })));
22694
- } catch (error2) {
22695
- return errorResult(error2);
22696
- }
22697
- }
22698
- );
22699
- server.registerTool(
22700
- "ollama_manage",
22701
- {
22702
- description: "Use when: pulling an approved tag or stopping a loaded model. Do not use when: deleting/recommending. Inputs: action/model; timeoutSeconds is pull-only. Returns: lifecycle result. Next: inspect models.",
22703
- inputSchema: {
22704
- action: external_exports.enum(["pull", "stop"]).describe('"pull" = disk, "stop" = memory'),
22705
- model: external_exports.string().min(1),
22706
- ollamaEndpoint: ollamaEndpointParam,
22707
- timeoutSeconds: external_exports.number().int().positive().optional().describe(`"pull" only. Defaults to ${DEFAULT_PULL_TIMEOUT_SECONDS}s.`)
22708
- },
22709
- outputSchema: manageResultSchema,
22710
- annotations: { destructiveHint: false }
22711
- },
22712
- async ({ action, model, ollamaEndpoint, timeoutSeconds }) => {
22713
- try {
22714
- if (action === "stop" && timeoutSeconds !== void 0) {
22715
- return errorResult(new Error('`timeoutSeconds` is valid only for action "pull".'));
22716
- }
22717
- const data = action === "pull" ? await ollamaPull(ollamaEndpoint, model, timeoutSeconds) : await ollamaFetch(ollamaEndpoint, "/api/generate", {
22718
- method: "POST",
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
- server.registerTool(
22755
- "delegate_research",
22756
- {
22757
- description: "Use when: a narrow lookup needs an allowed workspace. Do not use when: mutation, broad judgment, or outside-workspace facts are needed. Pages tool evidence instead of flooding caller context. Returns: answer, citations, evidence, verdict. Next: verify or escalate yourself.",
22758
- inputSchema: {
22759
- question: external_exports.string().min(1).describe("narrow and self-contained, answerable by reading files"),
22760
- workspacePath: external_exports.string().min(1).describe("directory inside FREELLAMA_MCP_ALLOWED_ROOTS (absolute preferred; relative is resolved)"),
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
- executionPreference: executionPreferenceParam,
22765
- minPlacementEvidence: minPlacementEvidenceParam,
22766
- legacyText: external_exports.boolean().optional().describe("false default: compact text cue; true: legacy serialized JSON text"),
22767
- agent: external_exports.object({
22768
- maxTurns: external_exports.number().int().positive().optional(),
22769
- contextTokens: external_exports.number().int().positive().optional().describe("Total agent context; input reserves outputTokens and safety margin."),
22770
- outputTokens: external_exports.number().int().positive().optional().describe("Maximum generated tokens per model call; reserved from context."),
22771
- temperature: external_exports.number().nonnegative().optional(),
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
- resolvedWorkspace = await assertAllowedWorkspace(workspacePath);
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
- const known = MODEL_EVIDENCE[chosenModel];
22807
- if (known?.grade === "unusable") {
22808
- return structuredResult({
22809
- adapter: chosenAdapter,
22810
- verification: assessDelegatedAnswer(question, 0, chosenModel),
22811
- answer: "",
22812
- toolCallCount: 0,
22813
- usage: { inputTokens: null, outputTokens: null },
22814
- telemetry: costTelemetry({ inputTokens: null, outputTokens: null }, EXTERNAL_COST),
22815
- evidence: [],
22816
- summary: `Refused before running: ${chosenModel} is measured unusable for research here (${known.note}). Re-run with a ~27B model (see README), or answer it yourself.`
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
- const dir = await mkdtemp(path4.join(tmpdir(), "freellama-delegate-"));
22820
- const promptFile = path4.join(dir, "prompt.md");
22821
- const resultFile = path4.join(dir, "result.json");
22822
- try {
22823
- await writeFile(promptFile, `${question}
22824
- `, "utf8");
22825
- const adapter2 = RESEARCH_ADAPTERS[chosenAdapter];
22826
- if (!existsSync4(adapter2)) {
22827
- return errorResult(
22828
- new Error(
22829
- `research adapter not found at ${adapter2}. In a published install it should be bundled under <package>/adapters; in-repo it comes from benchmark/local/scripts. Reinstall, or run \`npm run build\` from packages/mcp/ to re-copy it.`
22830
- )
22831
- );
22832
- }
22833
- const running = execFileAsync("python3", [adapter2], {
22834
- env: {
22835
- ...process.env,
22836
- FREELLAMA_TARGET_MODEL: chosenModel,
22837
- FREELLAMA_AGENT_MANAGED_ENDPOINT: endpoint ?? DEFAULT_SERVE_ENDPOINT,
22838
- FREELLAMA_AGENT_EXECUTION_PREFERENCE: executionPreference ?? "auto",
22839
- FREELLAMA_AGENT_MIN_PLACEMENT_EVIDENCE: minPlacementEvidence ?? "configured",
22840
- FREELLAMA_AGENT_TOKEN_CALIBRATION_DIR: DEFAULT_TOKEN_CALIBRATION_DIR,
22841
- FREELLAMA_BENCH_WORKSPACE: resolvedWorkspace,
22842
- FREELLAMA_BENCH_PROMPT: promptFile,
22843
- FREELLAMA_AGENT_RESULT: resultFile,
22844
- FREELLAMA_AGENT_MAX_TURNS: String(agent?.maxTurns ?? DEFAULT_DELEGATE_MAX_TURNS),
22845
- ...agent?.contextTokens !== void 0 ? { FREELLAMA_AGENT_NUM_CTX: String(agent.contextTokens) } : {},
22846
- ...agent?.outputTokens !== void 0 ? { FREELLAMA_AGENT_NUM_PREDICT: String(agent.outputTokens) } : {},
22847
- ...agent?.temperature !== void 0 ? { FREELLAMA_AGENT_TEMPERATURE: String(agent.temperature) } : {},
22848
- ...agent?.seed !== void 0 ? { FREELLAMA_AGENT_SEED: String(agent.seed) } : {},
22849
- ...agent?.think !== void 0 ? { FREELLAMA_AGENT_THINK: String(agent.think) } : {},
22850
- ...agent?.keepAlive !== void 0 ? { FREELLAMA_AGENT_KEEP_ALIVE: agent.keepAlive } : {},
22851
- ...agent?.requestTimeoutSeconds !== void 0 ? { FREELLAMA_AGENT_REQUEST_TIMEOUT_SECONDS: String(agent.requestTimeoutSeconds) } : {},
22852
- ...agent?.toolTimeoutSeconds !== void 0 ? { FREELLAMA_AGENT_TOOL_TIMEOUT_SECONDS: String(agent.toolTimeoutSeconds) } : {},
22853
- ...agent?.retryAttempts !== void 0 ? { FREELLAMA_AGENT_RETRY_ATTEMPTS: String(agent.retryAttempts) } : {},
22854
- ...agent?.retryBackoffSeconds !== void 0 ? { FREELLAMA_AGENT_RETRY_BACKOFF_SECONDS: String(agent.retryBackoffSeconds) } : {},
22855
- ...agent?.maxParseRepairs !== void 0 ? { FREELLAMA_AGENT_MAX_PARSE_REPAIRS: String(agent.maxParseRepairs) } : {},
22856
- ...agent?.parseRepairEchoChars !== void 0 ? { FREELLAMA_AGENT_PARSE_REPAIR_ECHO_CHARS: String(agent.parseRepairEchoChars) } : {},
22857
- ...agent?.context?.charsPerToken !== void 0 ? { FREELLAMA_AGENT_CHARS_PER_TOKEN: String(agent.context.charsPerToken) } : {},
22858
- ...agent?.context?.safetyMarginTokens !== void 0 ? { FREELLAMA_AGENT_SAFETY_MARGIN_TOKENS: String(agent.context.safetyMarginTokens) } : {},
22859
- ...agent?.context?.imageTokenEstimate !== void 0 ? { FREELLAMA_AGENT_IMAGE_TOKEN_ESTIMATE: String(agent.context.imageTokenEstimate) } : {},
22860
- ...agent?.context?.keepRecent !== void 0 ? { FREELLAMA_AGENT_KEEP_RECENT: String(agent.context.keepRecent) } : {},
22861
- ...agent?.context?.compactPreviewChars !== void 0 ? { FREELLAMA_AGENT_COMPACT_PREVIEW_CHARS: String(agent.context.compactPreviewChars) } : {},
22862
- ...agent?.context?.compactRetainRatio !== void 0 ? { FREELLAMA_AGENT_COMPACT_RETAIN_RATIO: String(agent.context.compactRetainRatio) } : {},
22863
- ...agent?.context?.clipHeadRatio !== void 0 ? { FREELLAMA_AGENT_CLIP_HEAD_RATIO: String(agent.context.clipHeadRatio) } : {},
22864
- ...agent?.context?.observationPageChars !== void 0 ? { FREELLAMA_AGENT_OBSERVATION_PAGE_CHARS: String(agent.context.observationPageChars) } : {},
22865
- ...agent?.context?.pinnedOverflow !== void 0 ? { FREELLAMA_AGENT_PINNED_OVERFLOW: agent.context.pinnedOverflow } : {}
22866
- },
22867
- timeout: DEFAULT_DELEGATE_TIMEOUT_SECONDS * 1e3,
22868
- // The answer is read from `resultFile`, never from stdout — but execFile's default 1 MB
22869
- // maxBuffer still applies to the agent's own progress logging, and overflowing it kills
22870
- // the subprocess and surfaces as a research failure with no explanation. Give the logs
22871
- // room; nothing here is proportional to the size of the answer.
22872
- maxBuffer: 32 * 1024 * 1024,
22873
- // On timeout, don't negotiate: SIGTERM can be swallowed by a python process blocked in a
22874
- // long model call, which would leave the child (and its VRAM) alive past the deadline the
22875
- // caller was promised.
22876
- killSignal: "SIGKILL"
22877
- });
22878
- liveDelegates.add(running.child);
22879
- let adapterError = null;
22880
- try {
22881
- await running;
22882
- } catch (error2) {
22883
- adapterError = error2;
22884
- } finally {
22885
- liveDelegates.delete(running.child);
22886
- }
22887
- let result;
22888
- try {
22889
- result = parseAdapterResult(await readFile(resultFile, "utf8"));
22890
- } catch {
22891
- return errorResult(
22892
- adapterError ?? new Error(
22893
- `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.`
22894
- )
22895
- );
22896
- }
22897
- const evidence = result.tool_calls.map((call, index) => {
22898
- const target = call.arguments?.queries?.path;
22899
- const rawDetail = call.arguments?.command ?? null;
22900
- const detail = rawDetail ? clipText(rawDetail, 400) : null;
22901
- return {
22902
- step: index + 1,
22903
- tool: call.arguments?.tool ?? call.raw_name ?? "?",
22904
- // The adapters record "ok" | "error" | "repeat" per call. Carrying it through is what
22905
- // lets a reader tell a run that read three files from one that failed three commands —
22906
- // indistinguishable in the trail before, and graded identically.
22907
- status: call.status ?? "ok",
22908
- path: target ? path4.relative(resolvedWorkspace, target) : detail ? extractExistingWorkspacePath(detail, resolvedWorkspace) : null,
22909
- detail,
22910
- detail_truncated: rawDetail !== detail
22911
- };
22912
- });
22913
- const succeeded = evidence.filter((step) => step.status === "ok");
22914
- const failed = evidence.length - succeeded.length;
22915
- const evidenceText = evidence.map(
22916
- (step) => ` ${step.step}. ${step.tool}${step.status === "ok" ? "" : ` [${step.status}]`}${step.path ? ` -> ${step.path}` : ""}${step.detail ? `: ${clipText(step.detail, 400)}` : ""}`
22917
- ).join("\n");
22918
- const citations = succeeded.map((step) => ({
22919
- step: step.step,
22920
- tool: step.tool,
22921
- path: step.path,
22922
- command: step.detail,
22923
- command_truncated: step.detail_truncated
22924
- }));
22925
- if (adapterError) {
22926
- return errorResult(
22927
- new Error(
22928
- `research adapter failed: ${result.final_answer}` + (evidenceText ? `
22929
- Evidence collected before the failure:
22930
- ${evidenceText}` : "")
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
- const verification = assessDelegatedAnswer(question, succeeded.length, chosenModel);
22935
- 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.`;
22936
- const payload = {
22937
- adapter: chosenAdapter,
22938
- verification,
22939
- answer: result.final_answer,
22940
- // recommendation + why live under `verification`; `citations` completes the triple so a
22941
- // caller never has to read `summary` to act on the result.
22942
- citations,
22943
- toolCallCount: result.tool_calls.length,
22944
- successfulToolCallCount: succeeded.length,
22945
- usage: {
22946
- inputTokens: result.usage.input_tokens,
22947
- outputTokens: result.usage.output_tokens
22948
- },
22949
- telemetry: costTelemetry({
22950
- inputTokens: result.usage.input_tokens ?? null,
22951
- outputTokens: result.usage.output_tokens ?? null
22952
- }, EXTERNAL_COST),
22953
- contextManagement: result.model_metadata?.context_management ?? null,
22954
- execution: {
22955
- preference: executionPreference ?? "auto",
22956
- minPlacementEvidence: minPlacementEvidence ?? "configured",
22957
- receipts: result.model_metadata?.execution_receipts ?? []
22958
- },
22959
- evidence,
22960
- summary
22961
- };
22962
- return structuredResult(payload, { legacyJson: legacyText === true });
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
- return errorResult(error2);
22965
- } finally {
22966
- await rm(dir, { recursive: true, force: true });
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
- var transport = new StdioServerTransport();
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);