@octocodeai/freellama-mcp-server 0.5.0 → 0.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CONFIGURATION.md +106 -0
- package/DEVELOPING.md +112 -0
- package/LICENSE-MIT +1 -1
- package/README.md +133 -284
- package/dist/index.js +1514 -1409
- package/dist/research/bash-agent.js +2435 -0
- package/dist/research/octocode-agent.js +1639 -0
- package/docs/ARCHITECTURE.md +176 -101
- package/docs/CLI.md +94 -63
- package/docs/CPU_GPU_ROUTING.md +15 -14
- package/docs/MODEL_SELECTION.md +116 -5
- package/docs/MONITORING.md +26 -16
- package/docs/PRODUCTION.md +33 -26
- package/docs/README.md +9 -17
- package/docs/SCOPES_AND_WARMING.md +8 -9
- package/docs/TESTING.md +22 -35
- package/native/index.d.ts +179 -174
- package/native/index.js +3 -3
- package/package.json +12 -10
- package/adapters/agent_context.py +0 -850
- package/adapters/agent_transport.py +0 -215
- package/adapters/bash_agent.py +0 -345
- package/adapters/octocode_agent.py +0 -404
- package/adapters/shell_sandbox.py +0 -487
- package/docs/ECONOMICS.md +0 -110
- package/docs/INDEX.md +0 -21
- package/docs/MODEL_METADATA.md +0 -106
- package/docs/OLLAMA_SIDECAR.md +0 -78
- package/docs/PRODUCT_POSITIONING.md +0 -121
- package/docs/dev/ADR_RESOURCE_AWARE_BACKEND_ROUTING.md +0 -55
- package/docs/dev/FINDINGS_AND_POSITIONING.md +0 -290
- package/docs/dev/LOCAL_PERFORMANCE_TESTING.md +0 -298
- package/docs/dev/OLLAMA_SYSTEM_OPTIMIZATION.md +0 -255
package/dist/index.js
CHANGED
|
@@ -3837,7 +3837,7 @@ var require_fast_uri = __commonJS({
|
|
|
3837
3837
|
normalizeString(uri, options);
|
|
3838
3838
|
} else if (typeof uri === "object") {
|
|
3839
3839
|
uri = /** @type {T} */
|
|
3840
|
-
parse3(
|
|
3840
|
+
parse3(serialize(uri, options), options);
|
|
3841
3841
|
}
|
|
3842
3842
|
return uri;
|
|
3843
3843
|
}
|
|
@@ -3872,13 +3872,13 @@ var require_fast_uri = __commonJS({
|
|
|
3872
3872
|
throw new Error(resolved.error);
|
|
3873
3873
|
}
|
|
3874
3874
|
schemelessOptions.skipEscape = true;
|
|
3875
|
-
return
|
|
3875
|
+
return serialize(resolved, schemelessOptions);
|
|
3876
3876
|
}
|
|
3877
3877
|
function resolveComponent(base, relative, options, skipNormalization) {
|
|
3878
3878
|
const target = {};
|
|
3879
3879
|
if (!skipNormalization) {
|
|
3880
|
-
base = parse3(
|
|
3881
|
-
relative = parse3(
|
|
3880
|
+
base = parse3(serialize(base, options), options);
|
|
3881
|
+
relative = parse3(serialize(relative, options), options);
|
|
3882
3882
|
}
|
|
3883
3883
|
options = options || {};
|
|
3884
3884
|
if (!options.tolerant && relative.scheme) {
|
|
@@ -3932,7 +3932,7 @@ var require_fast_uri = __commonJS({
|
|
|
3932
3932
|
const normalizedB = normalizeComparableURI(uriB, options);
|
|
3933
3933
|
return normalizedA !== void 0 && normalizedB !== void 0 && normalizedA === normalizedB;
|
|
3934
3934
|
}
|
|
3935
|
-
function
|
|
3935
|
+
function serialize(cmpts, opts) {
|
|
3936
3936
|
const component = {
|
|
3937
3937
|
host: cmpts.host,
|
|
3938
3938
|
scheme: cmpts.scheme,
|
|
@@ -4186,7 +4186,7 @@ var require_fast_uri = __commonJS({
|
|
|
4186
4186
|
function normalizeStringWithStatus(uri, opts) {
|
|
4187
4187
|
const { parsed, malformedAuthorityOrPort, malformedPercentEncoding, malformedSchemeSpecific, malformedHost, malformedScheme } = parseWithStatus(uri, opts);
|
|
4188
4188
|
return {
|
|
4189
|
-
normalized: malformedAuthorityOrPort || malformedPercentEncoding || malformedSchemeSpecific || malformedHost || malformedScheme ? uri :
|
|
4189
|
+
normalized: malformedAuthorityOrPort || malformedPercentEncoding || malformedSchemeSpecific || malformedHost || malformedScheme ? uri : serialize(parsed, opts),
|
|
4190
4190
|
malformedAuthorityOrPort,
|
|
4191
4191
|
malformedPercentEncoding,
|
|
4192
4192
|
malformedSchemeSpecific,
|
|
@@ -4200,7 +4200,7 @@ var require_fast_uri = __commonJS({
|
|
|
4200
4200
|
}
|
|
4201
4201
|
let value;
|
|
4202
4202
|
try {
|
|
4203
|
-
value = typeof uri === "string" ? uri :
|
|
4203
|
+
value = typeof uri === "string" ? uri : serialize(uri, opts);
|
|
4204
4204
|
} catch {
|
|
4205
4205
|
return void 0;
|
|
4206
4206
|
}
|
|
@@ -4213,7 +4213,7 @@ var require_fast_uri = __commonJS({
|
|
|
4213
4213
|
resolve,
|
|
4214
4214
|
resolveComponent,
|
|
4215
4215
|
equal,
|
|
4216
|
-
serialize
|
|
4216
|
+
serialize,
|
|
4217
4217
|
parse: parse3
|
|
4218
4218
|
};
|
|
4219
4219
|
module.exports = fastUri;
|
|
@@ -7199,16 +7199,6 @@ var require_dist = __commonJS({
|
|
|
7199
7199
|
}
|
|
7200
7200
|
});
|
|
7201
7201
|
|
|
7202
|
-
// src/index.ts
|
|
7203
|
-
import { execFile } from "node:child_process";
|
|
7204
|
-
import { existsSync as existsSync5, readdirSync } from "node:fs";
|
|
7205
|
-
import { mkdtemp, readFile, rm, writeFile } from "node:fs/promises";
|
|
7206
|
-
import { createHash } from "node:crypto";
|
|
7207
|
-
import { tmpdir } from "node:os";
|
|
7208
|
-
import path5 from "node:path";
|
|
7209
|
-
import { fileURLToPath as fileURLToPath3 } from "node:url";
|
|
7210
|
-
import { promisify } from "node:util";
|
|
7211
|
-
|
|
7212
7202
|
// ../../node_modules/zod/v3/external.js
|
|
7213
7203
|
var external_exports = {};
|
|
7214
7204
|
__export(external_exports, {
|
|
@@ -7700,15 +7690,15 @@ var makeIssue = (params) => {
|
|
|
7700
7690
|
message: issueData.message
|
|
7701
7691
|
};
|
|
7702
7692
|
}
|
|
7703
|
-
let
|
|
7693
|
+
let errorMessage2 = "";
|
|
7704
7694
|
const maps = errorMaps.filter((m) => !!m).slice().reverse();
|
|
7705
7695
|
for (const map of maps) {
|
|
7706
|
-
|
|
7696
|
+
errorMessage2 = map(fullIssue, { data, defaultError: errorMessage2 }).message;
|
|
7707
7697
|
}
|
|
7708
7698
|
return {
|
|
7709
7699
|
...issueData,
|
|
7710
7700
|
path: fullPath,
|
|
7711
|
-
message:
|
|
7701
|
+
message: errorMessage2
|
|
7712
7702
|
};
|
|
7713
7703
|
};
|
|
7714
7704
|
var EMPTY_PATH = [];
|
|
@@ -17546,19 +17536,19 @@ var getRefs = (options) => {
|
|
|
17546
17536
|
};
|
|
17547
17537
|
|
|
17548
17538
|
// ../../node_modules/zod-to-json-schema/dist/esm/errorMessages.js
|
|
17549
|
-
function addErrorMessage(res, key,
|
|
17539
|
+
function addErrorMessage(res, key, errorMessage2, refs) {
|
|
17550
17540
|
if (!refs?.errorMessages)
|
|
17551
17541
|
return;
|
|
17552
|
-
if (
|
|
17542
|
+
if (errorMessage2) {
|
|
17553
17543
|
res.errorMessage = {
|
|
17554
17544
|
...res.errorMessage,
|
|
17555
|
-
[key]:
|
|
17545
|
+
[key]: errorMessage2
|
|
17556
17546
|
};
|
|
17557
17547
|
}
|
|
17558
17548
|
}
|
|
17559
|
-
function setResponseValueAndErrors(res, key, value,
|
|
17549
|
+
function setResponseValueAndErrors(res, key, value, errorMessage2, refs) {
|
|
17560
17550
|
res[key] = value;
|
|
17561
|
-
addErrorMessage(res, key,
|
|
17551
|
+
addErrorMessage(res, key, errorMessage2, refs);
|
|
17562
17552
|
}
|
|
17563
17553
|
|
|
17564
17554
|
// ../../node_modules/zod-to-json-schema/dist/esm/getRelativePath.js
|
|
@@ -18869,8 +18859,8 @@ var Protocol = class {
|
|
|
18869
18859
|
if (queuedMessage.type === "response") {
|
|
18870
18860
|
resolver(message);
|
|
18871
18861
|
} else {
|
|
18872
|
-
const
|
|
18873
|
-
const error2 = new McpError(
|
|
18862
|
+
const errorMessage2 = message;
|
|
18863
|
+
const error2 = new McpError(errorMessage2.error.code, errorMessage2.error.message, errorMessage2.error.data);
|
|
18874
18864
|
resolver(error2);
|
|
18875
18865
|
}
|
|
18876
18866
|
} else {
|
|
@@ -20161,23 +20151,23 @@ var Server = class extends Protocol {
|
|
|
20161
20151
|
const wrappedHandler = async (request, extra) => {
|
|
20162
20152
|
const validatedRequest = safeParse2(CallToolRequestSchema, request);
|
|
20163
20153
|
if (!validatedRequest.success) {
|
|
20164
|
-
const
|
|
20165
|
-
throw new McpError(ErrorCode.InvalidParams, `Invalid tools/call request: ${
|
|
20154
|
+
const errorMessage2 = validatedRequest.error instanceof Error ? validatedRequest.error.message : String(validatedRequest.error);
|
|
20155
|
+
throw new McpError(ErrorCode.InvalidParams, `Invalid tools/call request: ${errorMessage2}`);
|
|
20166
20156
|
}
|
|
20167
20157
|
const { params } = validatedRequest.data;
|
|
20168
20158
|
const result = await Promise.resolve(handler(request, extra));
|
|
20169
20159
|
if (params.task) {
|
|
20170
20160
|
const taskValidationResult = safeParse2(CreateTaskResultSchema, result);
|
|
20171
20161
|
if (!taskValidationResult.success) {
|
|
20172
|
-
const
|
|
20173
|
-
throw new McpError(ErrorCode.InvalidParams, `Invalid task creation result: ${
|
|
20162
|
+
const errorMessage2 = taskValidationResult.error instanceof Error ? taskValidationResult.error.message : String(taskValidationResult.error);
|
|
20163
|
+
throw new McpError(ErrorCode.InvalidParams, `Invalid task creation result: ${errorMessage2}`);
|
|
20174
20164
|
}
|
|
20175
20165
|
return taskValidationResult.data;
|
|
20176
20166
|
}
|
|
20177
20167
|
const validationResult = safeParse2(CallToolResultSchema, result);
|
|
20178
20168
|
if (!validationResult.success) {
|
|
20179
|
-
const
|
|
20180
|
-
throw new McpError(ErrorCode.InvalidParams, `Invalid tools/call result: ${
|
|
20169
|
+
const errorMessage2 = validationResult.error instanceof Error ? validationResult.error.message : String(validationResult.error);
|
|
20170
|
+
throw new McpError(ErrorCode.InvalidParams, `Invalid tools/call result: ${errorMessage2}`);
|
|
20181
20171
|
}
|
|
20182
20172
|
return validationResult.data;
|
|
20183
20173
|
};
|
|
@@ -20671,12 +20661,12 @@ var McpServer = class {
|
|
|
20671
20661
|
* @param errorMessage - The error message.
|
|
20672
20662
|
* @returns The tool error result.
|
|
20673
20663
|
*/
|
|
20674
|
-
createToolError(
|
|
20664
|
+
createToolError(errorMessage2) {
|
|
20675
20665
|
return {
|
|
20676
20666
|
content: [
|
|
20677
20667
|
{
|
|
20678
20668
|
type: "text",
|
|
20679
|
-
text:
|
|
20669
|
+
text: errorMessage2
|
|
20680
20670
|
}
|
|
20681
20671
|
],
|
|
20682
20672
|
isError: true
|
|
@@ -20694,8 +20684,8 @@ var McpServer = class {
|
|
|
20694
20684
|
const parseResult = await safeParseAsync2(schemaToParse, args);
|
|
20695
20685
|
if (!parseResult.success) {
|
|
20696
20686
|
const error2 = "error" in parseResult ? parseResult.error : "Unknown error";
|
|
20697
|
-
const
|
|
20698
|
-
throw new McpError(ErrorCode.InvalidParams, `Input validation error: Invalid arguments for tool ${toolName}: ${
|
|
20687
|
+
const errorMessage2 = getParseErrorMessage(error2);
|
|
20688
|
+
throw new McpError(ErrorCode.InvalidParams, `Input validation error: Invalid arguments for tool ${toolName}: ${errorMessage2}`);
|
|
20699
20689
|
}
|
|
20700
20690
|
return parseResult.data;
|
|
20701
20691
|
}
|
|
@@ -20719,8 +20709,8 @@ var McpServer = class {
|
|
|
20719
20709
|
const parseResult = await safeParseAsync2(outputObj, result.structuredContent);
|
|
20720
20710
|
if (!parseResult.success) {
|
|
20721
20711
|
const error2 = "error" in parseResult ? parseResult.error : "Unknown error";
|
|
20722
|
-
const
|
|
20723
|
-
throw new McpError(ErrorCode.InvalidParams, `Output validation error: Invalid structured content for tool ${toolName}: ${
|
|
20712
|
+
const errorMessage2 = getParseErrorMessage(error2);
|
|
20713
|
+
throw new McpError(ErrorCode.InvalidParams, `Output validation error: Invalid structured content for tool ${toolName}: ${errorMessage2}`);
|
|
20724
20714
|
}
|
|
20725
20715
|
}
|
|
20726
20716
|
/**
|
|
@@ -20932,8 +20922,8 @@ var McpServer = class {
|
|
|
20932
20922
|
const parseResult = await safeParseAsync2(argsObj, request.params.arguments);
|
|
20933
20923
|
if (!parseResult.success) {
|
|
20934
20924
|
const error2 = "error" in parseResult ? parseResult.error : "Unknown error";
|
|
20935
|
-
const
|
|
20936
|
-
throw new McpError(ErrorCode.InvalidParams, `Invalid arguments for prompt ${request.params.name}: ${
|
|
20925
|
+
const errorMessage2 = getParseErrorMessage(error2);
|
|
20926
|
+
throw new McpError(ErrorCode.InvalidParams, `Invalid arguments for prompt ${request.params.name}: ${errorMessage2}`);
|
|
20937
20927
|
}
|
|
20938
20928
|
const args = parseResult.data;
|
|
20939
20929
|
const cb = prompt.callback;
|
|
@@ -21443,33 +21433,47 @@ var StdioServerTransport = class {
|
|
|
21443
21433
|
}
|
|
21444
21434
|
};
|
|
21445
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
|
+
|
|
21446
21452
|
// src/config.ts
|
|
21447
21453
|
import { existsSync } from "node:fs";
|
|
21448
21454
|
import { realpath } from "node:fs/promises";
|
|
21449
21455
|
import path from "node:path";
|
|
21450
21456
|
import { fileURLToPath } from "node:url";
|
|
21451
21457
|
import { homedir } from "node:os";
|
|
21458
|
+
var PACKAGE_ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
|
|
21452
21459
|
function findRepoRoot() {
|
|
21453
|
-
const packageRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
|
|
21454
21460
|
let dir = path.dirname(fileURLToPath(import.meta.url));
|
|
21455
21461
|
for (let depth = 0; depth < 10; depth += 1) {
|
|
21456
|
-
if (existsSync(path.join(dir, "Cargo.toml")) && path.join(dir, "packages", "mcp") ===
|
|
21462
|
+
if (existsSync(path.join(dir, "Cargo.toml")) && path.join(dir, "packages", "mcp") === PACKAGE_ROOT) return dir;
|
|
21457
21463
|
const parent = path.dirname(dir);
|
|
21458
21464
|
if (parent === dir) break;
|
|
21459
21465
|
dir = parent;
|
|
21460
21466
|
}
|
|
21461
21467
|
return void 0;
|
|
21462
21468
|
}
|
|
21463
|
-
var
|
|
21464
|
-
var
|
|
21469
|
+
var CHECKOUT_ROOT = findRepoRoot();
|
|
21470
|
+
var REPO_ROOT = CHECKOUT_ROOT ?? PACKAGE_ROOT;
|
|
21465
21471
|
function adapterPath(file) {
|
|
21466
|
-
|
|
21467
|
-
if (existsSync(bundled)) return bundled;
|
|
21468
|
-
return path.join(REPO_ROOT, "benchmark/local/scripts", file);
|
|
21472
|
+
return path.join(PACKAGE_ROOT, "dist", "research", file);
|
|
21469
21473
|
}
|
|
21470
21474
|
var RESEARCH_ADAPTERS = {
|
|
21471
|
-
bash: adapterPath("
|
|
21472
|
-
octocode: adapterPath("
|
|
21475
|
+
bash: adapterPath("bash-agent.js"),
|
|
21476
|
+
octocode: adapterPath("octocode-agent.js")
|
|
21473
21477
|
};
|
|
21474
21478
|
var DEFAULT_RESEARCH_ADAPTER = process.env.FREELLAMA_MCP_DEFAULT_ADAPTER === "octocode" ? "octocode" : "bash";
|
|
21475
21479
|
function envInt(name, fallback) {
|
|
@@ -21494,7 +21498,7 @@ function parseAllowedResearchRoots(raw, delimiter = path.delimiter) {
|
|
|
21494
21498
|
return raw.split(delimiter).filter(Boolean).map((root) => path.resolve(root));
|
|
21495
21499
|
}
|
|
21496
21500
|
var ALLOWED_RESEARCH_ROOTS = parseAllowedResearchRoots(
|
|
21497
|
-
process.env.FREELLAMA_MCP_ALLOWED_ROOTS ??
|
|
21501
|
+
process.env.FREELLAMA_MCP_ALLOWED_ROOTS ?? CHECKOUT_ROOT ?? ""
|
|
21498
21502
|
);
|
|
21499
21503
|
var resolvedRootsPromise = null;
|
|
21500
21504
|
function allowedResearchRoots() {
|
|
@@ -21566,8 +21570,7 @@ var DELEGATE_ENV_ALLOWLIST = /* @__PURE__ */ new Set([
|
|
|
21566
21570
|
"REQUESTS_CA_BUNDLE",
|
|
21567
21571
|
"XDG_CACHE_HOME",
|
|
21568
21572
|
"XDG_DATA_HOME",
|
|
21569
|
-
"npm_config_cache"
|
|
21570
|
-
"PYTHONPATH"
|
|
21573
|
+
"npm_config_cache"
|
|
21571
21574
|
]);
|
|
21572
21575
|
function delegateEnvironment(source = process.env) {
|
|
21573
21576
|
const env = {};
|
|
@@ -21577,18 +21580,7 @@ function delegateEnvironment(source = process.env) {
|
|
|
21577
21580
|
return env;
|
|
21578
21581
|
}
|
|
21579
21582
|
|
|
21580
|
-
// src/native.ts
|
|
21581
|
-
import { createRequire } from "node:module";
|
|
21582
|
-
import native from "../native/index.js";
|
|
21583
|
-
var { createScope, getScope, forkScope, deleteScope, warmModelRequest, doctor, machine, health, status, usage, createSession, deleteSession, killSession, listModels, route, runTask, runTaskRequest, runTaskBatchRequest, listTaskJobs, getTaskJob, cancelTaskJob, removeTaskJob } = native;
|
|
21584
|
-
var { version: SERVER_VERSION } = createRequire(import.meta.url)("../package.json");
|
|
21585
|
-
|
|
21586
21583
|
// src/serve.ts
|
|
21587
|
-
import { spawn } from "node:child_process";
|
|
21588
|
-
import { existsSync as existsSync2 } from "node:fs";
|
|
21589
|
-
import { createRequire as createRequire2 } from "node:module";
|
|
21590
|
-
import path2 from "node:path";
|
|
21591
|
-
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
21592
21584
|
var HEALTH_PATH = "/_freellama/v1/health";
|
|
21593
21585
|
var STARTUP_TIMEOUT_MS = 15e3;
|
|
21594
21586
|
var child = null;
|
|
@@ -21637,6 +21629,12 @@ function serveBinaryCandidates() {
|
|
|
21637
21629
|
if (process.env.FREELLAMA_SERVE_BINARY) candidates.unshift(process.env.FREELLAMA_SERVE_BINARY);
|
|
21638
21630
|
return candidates;
|
|
21639
21631
|
}
|
|
21632
|
+
function startServeInBackground() {
|
|
21633
|
+
ensureServe(void 0).catch((error2) => {
|
|
21634
|
+
process.stderr.write(`[freellama serve] background start failed: ${error2 instanceof Error ? error2.message : String(error2)}
|
|
21635
|
+
`);
|
|
21636
|
+
});
|
|
21637
|
+
}
|
|
21640
21638
|
function stopAutostartedServe() {
|
|
21641
21639
|
if (child && child.exitCode === null) child.kill("SIGTERM");
|
|
21642
21640
|
child = null;
|
|
@@ -21684,35 +21682,159 @@ async function ensureServe(endpoint) {
|
|
|
21684
21682
|
await starting;
|
|
21685
21683
|
}
|
|
21686
21684
|
|
|
21687
|
-
// src/
|
|
21688
|
-
|
|
21689
|
-
|
|
21690
|
-
function
|
|
21691
|
-
|
|
21692
|
-
try {
|
|
21693
|
-
root = realpathSync(workspace);
|
|
21694
|
-
} catch {
|
|
21695
|
-
return null;
|
|
21696
|
-
}
|
|
21697
|
-
const tokens = command.match(/(?:[^\s"'\\]+|"(?:\\.|[^"])*"|'[^']*')+/g) ?? [];
|
|
21698
|
-
const patternFlags = /* @__PURE__ */ new Set(["-name", "-iname", "-path", "-ipath", "-regex", "-iregex", "-wholename"]);
|
|
21699
|
-
for (let index = 0; index < tokens.length; index += 1) {
|
|
21700
|
-
const raw = tokens[index] ?? "";
|
|
21701
|
-
if (index > 0 && patternFlags.has(tokens[index - 1] ?? "")) continue;
|
|
21702
|
-
const token = raw.replace(/^["']|["']$/g, "").replace(/[|;&)]+$/g, "").replace(/:\d+(?::\d+)?$/, "");
|
|
21703
|
-
if (!token || token.startsWith("-") || /[*?$`]/.test(token)) continue;
|
|
21704
|
-
const candidate = path3.isAbsolute(token) ? token : path3.resolve(root, token);
|
|
21705
|
-
if (!existsSync3(candidate)) continue;
|
|
21685
|
+
// src/lifecycle.ts
|
|
21686
|
+
var liveDelegates = /* @__PURE__ */ new Set();
|
|
21687
|
+
var DETACH_DELEGATES = process.platform !== "win32";
|
|
21688
|
+
function killDelegateTree(child2) {
|
|
21689
|
+
if (DETACH_DELEGATES && child2.pid !== void 0) {
|
|
21706
21690
|
try {
|
|
21707
|
-
|
|
21708
|
-
|
|
21709
|
-
const withinRoot = relative && relative !== ".." && !relative.startsWith(`..${path3.sep}`);
|
|
21710
|
-
if (withinRoot && statSync(resolved).isFile()) return relative;
|
|
21691
|
+
process.kill(-child2.pid, "SIGKILL");
|
|
21692
|
+
return;
|
|
21711
21693
|
} catch {
|
|
21712
21694
|
}
|
|
21713
21695
|
}
|
|
21714
|
-
|
|
21696
|
+
try {
|
|
21697
|
+
child2.kill("SIGKILL");
|
|
21698
|
+
} catch {
|
|
21699
|
+
}
|
|
21700
|
+
}
|
|
21701
|
+
function runDelegate(file, args, options) {
|
|
21702
|
+
const child2 = spawn2(file, args, { env: options.env, detached: DETACH_DELEGATES, stdio: ["ignore", "pipe", "pipe"] });
|
|
21703
|
+
const done = new Promise((resolve, reject) => {
|
|
21704
|
+
const out = [];
|
|
21705
|
+
const err = [];
|
|
21706
|
+
let outLen = 0;
|
|
21707
|
+
let errLen = 0;
|
|
21708
|
+
let failure;
|
|
21709
|
+
let timedOut = false;
|
|
21710
|
+
let settled = false;
|
|
21711
|
+
const fail = (error2) => {
|
|
21712
|
+
if (failure === void 0) failure = error2;
|
|
21713
|
+
killDelegateTree(child2);
|
|
21714
|
+
};
|
|
21715
|
+
const timer = setTimeout(() => {
|
|
21716
|
+
timedOut = true;
|
|
21717
|
+
killDelegateTree(child2);
|
|
21718
|
+
}, options.timeoutMs);
|
|
21719
|
+
const onAbort = () => {
|
|
21720
|
+
const abort = new Error("The operation was aborted", { cause: options.signal?.reason });
|
|
21721
|
+
abort.name = "AbortError";
|
|
21722
|
+
abort.code = "ABORT_ERR";
|
|
21723
|
+
fail(abort);
|
|
21724
|
+
};
|
|
21725
|
+
if (options.signal?.aborted) onAbort();
|
|
21726
|
+
else options.signal?.addEventListener("abort", onAbort, { once: true });
|
|
21727
|
+
const collect = (chunks, name) => (chunk) => {
|
|
21728
|
+
const length = name === "stdout" ? outLen += chunk.length : errLen += chunk.length;
|
|
21729
|
+
if (length > options.maxBuffer) {
|
|
21730
|
+
const overflow = new RangeError(`${name} maxBuffer length exceeded`);
|
|
21731
|
+
overflow.code = "ERR_CHILD_PROCESS_STDIO_MAXBUFFER";
|
|
21732
|
+
fail(overflow);
|
|
21733
|
+
return;
|
|
21734
|
+
}
|
|
21735
|
+
chunks.push(chunk);
|
|
21736
|
+
};
|
|
21737
|
+
child2.stdout.on("data", collect(out, "stdout"));
|
|
21738
|
+
child2.stderr.on("data", collect(err, "stderr"));
|
|
21739
|
+
const finish = (code, signal, spawnError) => {
|
|
21740
|
+
if (settled) return;
|
|
21741
|
+
settled = true;
|
|
21742
|
+
clearTimeout(timer);
|
|
21743
|
+
options.signal?.removeEventListener("abort", onAbort);
|
|
21744
|
+
const stdout = Buffer.concat(out).toString("utf8");
|
|
21745
|
+
const stderr = Buffer.concat(err).toString("utf8");
|
|
21746
|
+
if (spawnError ?? failure) {
|
|
21747
|
+
reject(spawnError ?? failure);
|
|
21748
|
+
return;
|
|
21749
|
+
}
|
|
21750
|
+
if (code === 0 && signal === null) {
|
|
21751
|
+
resolve({ stdout, stderr });
|
|
21752
|
+
return;
|
|
21753
|
+
}
|
|
21754
|
+
const error2 = new Error(`Command failed: ${[file, ...args].join(" ")}
|
|
21755
|
+
${stderr}`);
|
|
21756
|
+
Object.assign(error2, { code, killed: timedOut, signal, stdout, stderr });
|
|
21757
|
+
reject(error2);
|
|
21758
|
+
};
|
|
21759
|
+
child2.once("error", (error2) => finish(null, null, error2));
|
|
21760
|
+
child2.once("close", (code, signal) => finish(code, signal));
|
|
21761
|
+
});
|
|
21762
|
+
return { child: child2, done };
|
|
21763
|
+
}
|
|
21764
|
+
function killLiveDelegates() {
|
|
21765
|
+
for (const child2 of liveDelegates) killDelegateTree(child2);
|
|
21766
|
+
liveDelegates.clear();
|
|
21767
|
+
}
|
|
21768
|
+
function installProcessLifecycle() {
|
|
21769
|
+
for (const stream of [process.stdout, process.stderr]) {
|
|
21770
|
+
stream.on("error", (error2) => {
|
|
21771
|
+
if (error2.code === "EPIPE") process.exit(0);
|
|
21772
|
+
throw error2;
|
|
21773
|
+
});
|
|
21774
|
+
}
|
|
21775
|
+
process.on("exit", () => {
|
|
21776
|
+
killLiveDelegates();
|
|
21777
|
+
stopAutostartedServe();
|
|
21778
|
+
});
|
|
21779
|
+
process.stdin.on("close", () => {
|
|
21780
|
+
killLiveDelegates();
|
|
21781
|
+
stopAutostartedServe();
|
|
21782
|
+
process.exit(0);
|
|
21783
|
+
});
|
|
21784
|
+
for (const signal of ["SIGINT", "SIGTERM", "SIGHUP"]) {
|
|
21785
|
+
process.on(signal, () => {
|
|
21786
|
+
killLiveDelegates();
|
|
21787
|
+
process.exit(0);
|
|
21788
|
+
});
|
|
21789
|
+
}
|
|
21790
|
+
}
|
|
21791
|
+
|
|
21792
|
+
// src/schema-dialect.ts
|
|
21793
|
+
function stripSchemaDialect(message) {
|
|
21794
|
+
const tools = message.result?.tools;
|
|
21795
|
+
if (!Array.isArray(tools)) return message;
|
|
21796
|
+
for (const tool of tools) {
|
|
21797
|
+
delete tool.inputSchema?.$schema;
|
|
21798
|
+
delete tool.outputSchema?.$schema;
|
|
21799
|
+
}
|
|
21800
|
+
return message;
|
|
21801
|
+
}
|
|
21802
|
+
function withoutSchemaDialect(transport2) {
|
|
21803
|
+
const send = transport2.send.bind(transport2);
|
|
21804
|
+
transport2.send = (message, options) => send(stripSchemaDialect(message), options);
|
|
21805
|
+
return transport2;
|
|
21806
|
+
}
|
|
21807
|
+
|
|
21808
|
+
// src/docs-resources.ts
|
|
21809
|
+
import { existsSync as existsSync3, readdirSync } from "node:fs";
|
|
21810
|
+
import { readFile } from "node:fs/promises";
|
|
21811
|
+
import path3 from "node:path";
|
|
21812
|
+
import { fileURLToPath as fileURLToPath3 } from "node:url";
|
|
21813
|
+
function registerDocsResources(server2) {
|
|
21814
|
+
const PACKAGED_DOCS_DIR = path3.resolve(path3.dirname(fileURLToPath3(import.meta.url)), "..", "docs");
|
|
21815
|
+
if (!existsSync3(path3.join(PACKAGED_DOCS_DIR, "README.md"))) {
|
|
21816
|
+
throw new Error(`FreeLlama MCP documentation index is missing at ${PACKAGED_DOCS_DIR}/README.md; run the package build.`);
|
|
21817
|
+
}
|
|
21818
|
+
const packagedDocs = readdirSync(PACKAGED_DOCS_DIR).filter((name) => name.endsWith(".md")).sort();
|
|
21819
|
+
for (const name of packagedDocs) {
|
|
21820
|
+
const isIndex = name === "README.md";
|
|
21821
|
+
const slug = isIndex ? "index" : name.replace(/\.md$/, "");
|
|
21822
|
+
const uri = `freellama://docs/${slug}`;
|
|
21823
|
+
server2.registerResource(
|
|
21824
|
+
`freellama-docs-${slug.toLowerCase()}`,
|
|
21825
|
+
uri,
|
|
21826
|
+
{
|
|
21827
|
+
mimeType: "text/markdown",
|
|
21828
|
+
description: isIndex ? "Index of packaged FreeLlama operator and agent documentation. Read this first, then fetch one relevant document." : `Packaged FreeLlama documentation: ${name}.`
|
|
21829
|
+
},
|
|
21830
|
+
async () => ({
|
|
21831
|
+
contents: [{ uri, mimeType: "text/markdown", text: await readFile(path3.join(PACKAGED_DOCS_DIR, name), "utf8") }]
|
|
21832
|
+
})
|
|
21833
|
+
);
|
|
21834
|
+
}
|
|
21715
21835
|
}
|
|
21836
|
+
|
|
21837
|
+
// src/helpers.ts
|
|
21716
21838
|
async function ollamaFetch(endpoint, path6, init = {}) {
|
|
21717
21839
|
const base = (endpoint ?? DEFAULT_OLLAMA_ENDPOINT).replace(/\/$/, "");
|
|
21718
21840
|
const controller = new AbortController();
|
|
@@ -21759,62 +21881,6 @@ async function readNdjson(body, onLine) {
|
|
|
21759
21881
|
}
|
|
21760
21882
|
return text + decoder.decode();
|
|
21761
21883
|
}
|
|
21762
|
-
function summarizeOllamaPullStream(text) {
|
|
21763
|
-
const events = [];
|
|
21764
|
-
for (const line of text.split(/\r?\n/)) {
|
|
21765
|
-
const trimmed = line.trim();
|
|
21766
|
-
if (!trimmed) continue;
|
|
21767
|
-
try {
|
|
21768
|
-
const parsed = JSON.parse(trimmed);
|
|
21769
|
-
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
21770
|
-
events.push(parsed);
|
|
21771
|
-
}
|
|
21772
|
-
} catch {
|
|
21773
|
-
}
|
|
21774
|
-
}
|
|
21775
|
-
if (events.length === 0) {
|
|
21776
|
-
if (!text.trim()) return { status: "empty" };
|
|
21777
|
-
try {
|
|
21778
|
-
const parsed = JSON.parse(text);
|
|
21779
|
-
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
21780
|
-
return parsed;
|
|
21781
|
-
}
|
|
21782
|
-
} catch {
|
|
21783
|
-
return { status: "unparsed", raw: clipText(text, 500) };
|
|
21784
|
-
}
|
|
21785
|
-
return { status: "unparsed", raw: clipText(text, 500) };
|
|
21786
|
-
}
|
|
21787
|
-
const last = events[events.length - 1] ?? {};
|
|
21788
|
-
const withBytes = [...events].reverse().find((event) => typeof event.total === "number" && event.total > 0);
|
|
21789
|
-
const completed = typeof withBytes?.completed === "number" ? withBytes.completed : null;
|
|
21790
|
-
const total = typeof withBytes?.total === "number" ? withBytes.total : null;
|
|
21791
|
-
const percent = completed != null && total != null && total > 0 ? Math.round(completed / total * 1e3) / 10 : null;
|
|
21792
|
-
const status2 = typeof last.error === "string" && last.error ? "error" : last.status;
|
|
21793
|
-
return {
|
|
21794
|
-
...last,
|
|
21795
|
-
...status2 === void 0 ? {} : { status: status2 },
|
|
21796
|
-
progress: {
|
|
21797
|
-
events: events.length,
|
|
21798
|
-
completed,
|
|
21799
|
-
total,
|
|
21800
|
-
percent,
|
|
21801
|
-
lastStatus: last.status ?? null
|
|
21802
|
-
}
|
|
21803
|
-
};
|
|
21804
|
-
}
|
|
21805
|
-
async function ollamaPull(endpoint, model, timeoutSeconds, options = {}) {
|
|
21806
|
-
const raw = await ollamaFetch(endpoint, "/api/pull", {
|
|
21807
|
-
method: "POST",
|
|
21808
|
-
body: { model, stream: true },
|
|
21809
|
-
timeoutMs: (timeoutSeconds ?? DEFAULT_PULL_TIMEOUT_SECONDS) * 1e3,
|
|
21810
|
-
parse: "ndjson",
|
|
21811
|
-
signal: options.signal,
|
|
21812
|
-
onLine: options.onProgress
|
|
21813
|
-
});
|
|
21814
|
-
if (typeof raw === "string") return summarizeOllamaPullStream(raw);
|
|
21815
|
-
if (raw && typeof raw === "object" && !Array.isArray(raw)) return raw;
|
|
21816
|
-
return { status: "empty" };
|
|
21817
|
-
}
|
|
21818
21884
|
var endpointParam = external_exports.string().min(1).optional();
|
|
21819
21885
|
var ollamaEndpointParam = external_exports.string().min(1).optional();
|
|
21820
21886
|
var TASK_KINDS = [
|
|
@@ -21835,19 +21901,7 @@ function canonicalTaskKind(task) {
|
|
|
21835
21901
|
var objectiveParam = external_exports.enum(["fastest", "balanced", "quality"]).optional();
|
|
21836
21902
|
var executionPreferenceParam = external_exports.enum(["auto", "prefer_cpu", "prefer_gpu"]).optional();
|
|
21837
21903
|
var minPlacementEvidenceParam = external_exports.enum(["configured", "observed"]).optional();
|
|
21838
|
-
var CONFIDENCE_RANK = { low: 1, medium: 2 };
|
|
21839
21904
|
var minConfidenceParam = external_exports.enum(["low", "medium"]).optional();
|
|
21840
|
-
function belowConfidence(decision, minConfidence) {
|
|
21841
|
-
if (!minConfidence) return null;
|
|
21842
|
-
const actual = typeof decision.confidence === "string" ? decision.confidence : "low";
|
|
21843
|
-
if ((CONFIDENCE_RANK[actual] ?? 0) >= (CONFIDENCE_RANK[minConfidence] ?? 0)) return null;
|
|
21844
|
-
return errorResult(
|
|
21845
|
-
new Error(
|
|
21846
|
-
`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(", ")}].
|
|
21847
|
-
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.`
|
|
21848
|
-
)
|
|
21849
|
-
);
|
|
21850
|
-
}
|
|
21851
21905
|
var REQUIRED_CAPABILITIES = [
|
|
21852
21906
|
"completion",
|
|
21853
21907
|
"tools",
|
|
@@ -21880,7 +21934,8 @@ function taskMessages({ systemPrompt, messages, prompt, images }) {
|
|
|
21880
21934
|
return [{ role: "system", content: systemPrompt }, ...conversation];
|
|
21881
21935
|
}
|
|
21882
21936
|
var batchTaskParam = external_exports.object({
|
|
21883
|
-
task
|
|
21937
|
+
// Nested `task` shares the top-level description; repeating it re-sends the text every request.
|
|
21938
|
+
task: taskParam.removeDefault().optional(),
|
|
21884
21939
|
objective: objectiveParam,
|
|
21885
21940
|
model: external_exports.string().min(1).optional(),
|
|
21886
21941
|
sessionId: external_exports.string().uuid().optional(),
|
|
@@ -21893,6 +21948,7 @@ var batchTaskParam = external_exports.object({
|
|
|
21893
21948
|
priority: external_exports.enum(["interactive", "normal", "background"]).optional(),
|
|
21894
21949
|
maxWaitSeconds: external_exports.number().int().positive().optional(),
|
|
21895
21950
|
timeoutSeconds: external_exports.number().int().positive().optional(),
|
|
21951
|
+
stopOnRepetition: external_exports.boolean().optional().describe("Stream and stop once output repeats itself; keeps all generated text"),
|
|
21896
21952
|
prompt: external_exports.string().min(1).optional(),
|
|
21897
21953
|
systemPrompt: systemPromptParam,
|
|
21898
21954
|
messages: messagesParam,
|
|
@@ -21907,56 +21963,27 @@ var batchTaskParam = external_exports.object({
|
|
|
21907
21963
|
topLogprobs: external_exports.number().int().nonnegative().optional(),
|
|
21908
21964
|
minConfidence: minConfidenceParam
|
|
21909
21965
|
}).strict();
|
|
21966
|
+
function fresh(schema) {
|
|
21967
|
+
const def = { ...schema._def };
|
|
21968
|
+
for (const [key, value] of Object.entries(def)) {
|
|
21969
|
+
if (value instanceof external_exports.ZodType) def[key] = fresh(value);
|
|
21970
|
+
else if (Array.isArray(value) && value.length > 0 && value.every((v) => v instanceof external_exports.ZodType)) {
|
|
21971
|
+
def[key] = value.map((v) => fresh(v));
|
|
21972
|
+
}
|
|
21973
|
+
}
|
|
21974
|
+
if (schema instanceof external_exports.ZodObject) {
|
|
21975
|
+
const shape = Object.fromEntries(Object.entries(schema.shape).map(([k, v]) => [k, fresh(v)]));
|
|
21976
|
+
def.shape = () => shape;
|
|
21977
|
+
}
|
|
21978
|
+
return new schema.constructor(def);
|
|
21979
|
+
}
|
|
21980
|
+
var BATCH_TASK_FIELDS = new Set(Object.keys(batchTaskParam.shape));
|
|
21910
21981
|
var batchItemParam = external_exports.object({
|
|
21911
21982
|
id: external_exports.string().min(1),
|
|
21912
21983
|
independent: external_exports.literal(true),
|
|
21913
|
-
task: batchTaskParam
|
|
21984
|
+
task: fresh(batchTaskParam)
|
|
21914
21985
|
}).strict();
|
|
21915
21986
|
var objectResultSchema = external_exports.object({}).passthrough();
|
|
21916
|
-
var doctorResultSchema = external_exports.object({
|
|
21917
|
-
summary: external_exports.string().optional(),
|
|
21918
|
-
endpoint: external_exports.string().optional(),
|
|
21919
|
-
scheduler: external_exports.unknown().optional()
|
|
21920
|
-
}).passthrough();
|
|
21921
|
-
var sessionResultSchema = external_exports.object({
|
|
21922
|
-
session_id: external_exports.string().optional(),
|
|
21923
|
-
deleted: external_exports.boolean().optional(),
|
|
21924
|
-
killed: external_exports.boolean().optional(),
|
|
21925
|
-
runner_stop_confirmed: external_exports.boolean().optional()
|
|
21926
|
-
}).passthrough();
|
|
21927
|
-
var manageResultSchema = external_exports.object({
|
|
21928
|
-
status: external_exports.string().optional(),
|
|
21929
|
-
progress: external_exports.unknown().optional()
|
|
21930
|
-
}).passthrough();
|
|
21931
|
-
var deleteResultSchema = external_exports.object({
|
|
21932
|
-
deleted: external_exports.string()
|
|
21933
|
-
}).passthrough();
|
|
21934
|
-
var modelsResultSchema = external_exports.object({
|
|
21935
|
-
models: external_exports.array(external_exports.record(external_exports.unknown())).optional(),
|
|
21936
|
-
tags: external_exports.array(external_exports.record(external_exports.unknown())).optional(),
|
|
21937
|
-
page: external_exports.unknown().optional(),
|
|
21938
|
-
knowledge: external_exports.unknown().optional(),
|
|
21939
|
-
library: external_exports.unknown().optional()
|
|
21940
|
-
}).passthrough();
|
|
21941
|
-
var taskResultSchema = external_exports.object({
|
|
21942
|
-
selected_model: external_exports.string().optional(),
|
|
21943
|
-
context_window_fit: external_exports.string().optional(),
|
|
21944
|
-
execution: external_exports.unknown().optional(),
|
|
21945
|
-
response: external_exports.unknown().optional(),
|
|
21946
|
-
telemetry: external_exports.unknown().optional()
|
|
21947
|
-
}).passthrough();
|
|
21948
|
-
var batchResultSchema = external_exports.object({
|
|
21949
|
-
results: external_exports.unknown().optional(),
|
|
21950
|
-
telemetry: external_exports.unknown().optional()
|
|
21951
|
-
}).passthrough();
|
|
21952
|
-
var researchResultSchema = external_exports.object({
|
|
21953
|
-
answer: external_exports.string(),
|
|
21954
|
-
summary: external_exports.string(),
|
|
21955
|
-
citations: external_exports.unknown().optional(),
|
|
21956
|
-
verification: external_exports.unknown(),
|
|
21957
|
-
usage: external_exports.unknown().optional(),
|
|
21958
|
-
telemetry: external_exports.unknown().optional()
|
|
21959
|
-
}).passthrough();
|
|
21960
21987
|
function configuredExternalCost(env = process.env) {
|
|
21961
21988
|
const model = env.FREELLAMA_EXTERNAL_COST_MODEL;
|
|
21962
21989
|
const inputRaw = env.FREELLAMA_EXTERNAL_COST_INPUT_USD_PER_M;
|
|
@@ -22013,11 +22040,6 @@ function clipText(text, limit) {
|
|
|
22013
22040
|
const head = Math.floor(usable * 2 / 3);
|
|
22014
22041
|
return text.slice(0, head) + marker + text.slice(-(usable - head));
|
|
22015
22042
|
}
|
|
22016
|
-
var PRETTY_PRINT_MAX_BYTES = 8 * 1024;
|
|
22017
|
-
function serialize(value) {
|
|
22018
|
-
const compact = JSON.stringify(value);
|
|
22019
|
-
return compact.length > PRETTY_PRINT_MAX_BYTES ? compact : JSON.stringify(value, null, 2);
|
|
22020
|
-
}
|
|
22021
22043
|
function resultSummary(value) {
|
|
22022
22044
|
const explicit = value.summary;
|
|
22023
22045
|
if (typeof explicit === "string" && explicit.trim()) return clipText(explicit.trim(), 500);
|
|
@@ -22036,7 +22058,7 @@ function taskAnswerText(payload) {
|
|
|
22036
22058
|
const message = record2(response.message);
|
|
22037
22059
|
if (message) {
|
|
22038
22060
|
const parts = [];
|
|
22039
|
-
if (typeof message.content === "string" && message.content.trim()) parts.push(message.content
|
|
22061
|
+
if (typeof message.content === "string" && message.content.trim()) parts.push(answerContent(message.content, payload));
|
|
22040
22062
|
if (Array.isArray(message.tool_calls) && message.tool_calls.length > 0) {
|
|
22041
22063
|
parts.push(`tool_calls: ${JSON.stringify(message.tool_calls)}`);
|
|
22042
22064
|
}
|
|
@@ -22052,6 +22074,15 @@ function taskAnswerText(payload) {
|
|
|
22052
22074
|
}
|
|
22053
22075
|
return void 0;
|
|
22054
22076
|
}
|
|
22077
|
+
function answerContent(content, payload) {
|
|
22078
|
+
const check2 = record2(payload.output_check);
|
|
22079
|
+
const keep = check2?.unique_prefix_chars;
|
|
22080
|
+
if (check2?.repetition_loop !== true || typeof keep !== "number") return content.trim();
|
|
22081
|
+
const characters = Array.from(content);
|
|
22082
|
+
const trimmed = characters.slice(0, keep).join("").trim();
|
|
22083
|
+
return `${trimmed}
|
|
22084
|
+
[freellama: trimmed ${characters.length - keep} repeated characters; the model looped until num_predict \u2014 treat as low confidence]`;
|
|
22085
|
+
}
|
|
22055
22086
|
function batchAnswerText(payload) {
|
|
22056
22087
|
if (!Array.isArray(payload.results)) return void 0;
|
|
22057
22088
|
const perItem = Math.max(512, Math.floor(ANSWER_TEXT_MAX_CHARS / Math.max(payload.results.length, 1)));
|
|
@@ -22067,7 +22098,7 @@ ${clipText(answer, perItem)}`;
|
|
|
22067
22098
|
return lines.join("\n\n");
|
|
22068
22099
|
}
|
|
22069
22100
|
function structuredResult(value, options = {}) {
|
|
22070
|
-
const text = options.text ??
|
|
22101
|
+
const text = options.text ?? resultSummary(value);
|
|
22071
22102
|
return {
|
|
22072
22103
|
content: [{ type: "text", text }],
|
|
22073
22104
|
structuredContent: value
|
|
@@ -22093,6 +22124,19 @@ ${clipText(raw, 2e3)}`
|
|
|
22093
22124
|
function isStructuredSuccess(result) {
|
|
22094
22125
|
return !("isError" in result && result.isError) && "structuredContent" in result && result.structuredContent !== void 0;
|
|
22095
22126
|
}
|
|
22127
|
+
var escapeRegExp = (text) => text.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
22128
|
+
var SERVE_ADDRESS_PATTERN = (() => {
|
|
22129
|
+
let url;
|
|
22130
|
+
try {
|
|
22131
|
+
url = new URL(DEFAULT_SERVE_ENDPOINT);
|
|
22132
|
+
} catch {
|
|
22133
|
+
return /_freellama/i;
|
|
22134
|
+
}
|
|
22135
|
+
const { hostname: hostname2, port } = url;
|
|
22136
|
+
const hosts = ["127.0.0.1", "localhost"].includes(hostname2) ? ["127.0.0.1", "localhost"] : [hostname2];
|
|
22137
|
+
const addresses = hosts.map((host) => escapeRegExp(port ? `${host}:${port}` : host));
|
|
22138
|
+
return new RegExp(`(?:_freellama|${addresses.join("|")})`, "i");
|
|
22139
|
+
})();
|
|
22096
22140
|
function errorResult(error2) {
|
|
22097
22141
|
const message = error2 instanceof Error ? error2.message : String(error2);
|
|
22098
22142
|
let receipt;
|
|
@@ -22103,7 +22147,7 @@ function errorResult(error2) {
|
|
|
22103
22147
|
}
|
|
22104
22148
|
} catch {
|
|
22105
22149
|
}
|
|
22106
|
-
const managedServeUnavailable = /(?:connection refused|connect error|failed to connect|error sending request)/i.test(message) &&
|
|
22150
|
+
const managedServeUnavailable = /(?:connection refused|connect error|failed to connect|error sending request)/i.test(message) && SERVE_ADDRESS_PATTERN.test(message);
|
|
22107
22151
|
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);
|
|
22108
22152
|
const actionableMessage = managedServeUnavailable ? `${message}
|
|
22109
22153
|
|
|
@@ -22116,37 +22160,6 @@ Ollama is not running at that address. Ask the user to start Ollama (the app, or
|
|
|
22116
22160
|
...receipt ? { structuredContent: receipt } : {}
|
|
22117
22161
|
};
|
|
22118
22162
|
}
|
|
22119
|
-
var adapterCallSchema = external_exports.object({
|
|
22120
|
-
raw_name: external_exports.string().optional(),
|
|
22121
|
-
status: external_exports.string().optional(),
|
|
22122
|
-
arguments: external_exports.object({
|
|
22123
|
-
tool: external_exports.string().optional(),
|
|
22124
|
-
command: external_exports.string().optional(),
|
|
22125
|
-
queries: external_exports.object({ path: external_exports.string().optional() }).passthrough().optional()
|
|
22126
|
-
}).passthrough().optional()
|
|
22127
|
-
}).passthrough();
|
|
22128
|
-
var adapterResultSchema = external_exports.object({
|
|
22129
|
-
final_answer: external_exports.string(),
|
|
22130
|
-
tool_calls: external_exports.array(adapterCallSchema).default([]),
|
|
22131
|
-
usage: external_exports.object({
|
|
22132
|
-
input_tokens: external_exports.number().nullable().optional(),
|
|
22133
|
-
output_tokens: external_exports.number().nullable().optional()
|
|
22134
|
-
}).passthrough().default({}),
|
|
22135
|
-
model_metadata: external_exports.object({
|
|
22136
|
-
context_compactions: external_exports.number().int().nonnegative().optional(),
|
|
22137
|
-
runtime_config: external_exports.record(external_exports.string(), external_exports.unknown()).optional(),
|
|
22138
|
-
context_management: external_exports.object({
|
|
22139
|
-
token_counting: external_exports.enum(["configured_estimate", "model_calibrated_estimate"]),
|
|
22140
|
-
estimate_scale: external_exports.number().positive(),
|
|
22141
|
-
calibration_samples: external_exports.number().int().nonnegative(),
|
|
22142
|
-
pinned_overflow: external_exports.enum(["error", "clip"]),
|
|
22143
|
-
compactions: external_exports.number().int().nonnegative()
|
|
22144
|
-
}).passthrough().optional()
|
|
22145
|
-
}).passthrough().optional()
|
|
22146
|
-
});
|
|
22147
|
-
function parseAdapterResult(raw) {
|
|
22148
|
-
return adapterResultSchema.parse(JSON.parse(raw));
|
|
22149
|
-
}
|
|
22150
22163
|
function summarizeEmbeddings(payload) {
|
|
22151
22164
|
const response = payload.response;
|
|
22152
22165
|
if (response === null || typeof response !== "object") return null;
|
|
@@ -22168,22 +22181,166 @@ function summarizeEmbeddings(payload) {
|
|
|
22168
22181
|
};
|
|
22169
22182
|
}
|
|
22170
22183
|
|
|
22171
|
-
// src/
|
|
22172
|
-
|
|
22173
|
-
|
|
22174
|
-
|
|
22175
|
-
|
|
22176
|
-
|
|
22177
|
-
|
|
22178
|
-
|
|
22179
|
-
|
|
22180
|
-
|
|
22181
|
-
|
|
22182
|
-
|
|
22183
|
-
|
|
22184
|
-
|
|
22185
|
-
|
|
22186
|
-
|
|
22184
|
+
// src/tools/doctor.ts
|
|
22185
|
+
var doctorResultSchema = external_exports.object({
|
|
22186
|
+
summary: external_exports.string().optional(),
|
|
22187
|
+
endpoint: external_exports.string().optional()
|
|
22188
|
+
}).passthrough();
|
|
22189
|
+
function registerDoctorTool(server2) {
|
|
22190
|
+
server2.registerTool(
|
|
22191
|
+
"doctor",
|
|
22192
|
+
{
|
|
22193
|
+
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.",
|
|
22194
|
+
inputSchema: external_exports.object({
|
|
22195
|
+
ollamaEndpoint: ollamaEndpointParam,
|
|
22196
|
+
serveEndpoint: endpointParam,
|
|
22197
|
+
view: external_exports.enum(["summary", "full", "status", "usage"]).optional(),
|
|
22198
|
+
days: external_exports.number().int().min(1).max(366).optional()
|
|
22199
|
+
}).strict(),
|
|
22200
|
+
outputSchema: doctorResultSchema,
|
|
22201
|
+
annotations: { readOnlyHint: true }
|
|
22202
|
+
},
|
|
22203
|
+
async ({ ollamaEndpoint, serveEndpoint, view, days }) => {
|
|
22204
|
+
try {
|
|
22205
|
+
if (days !== void 0 && view !== "usage") return errorResult(new Error("days is only valid with view: usage."));
|
|
22206
|
+
if (view === "status") return parsedResult(await status(serveEndpoint));
|
|
22207
|
+
if (view === "usage") return parsedResult(await usage(serveEndpoint, days));
|
|
22208
|
+
const report = parsedResult(await doctor(ollamaEndpoint));
|
|
22209
|
+
if (!isStructuredSuccess(report)) return report;
|
|
22210
|
+
try {
|
|
22211
|
+
report.structuredContent.machine = JSON.parse(await machine(serveEndpoint));
|
|
22212
|
+
delete report.structuredContent.machine_unavailable;
|
|
22213
|
+
} catch (error2) {
|
|
22214
|
+
if (report.structuredContent.machine == null) {
|
|
22215
|
+
report.structuredContent.machine_unavailable = `freellama serve unreachable, so no machine profile: ${error2 instanceof Error ? error2.message : String(error2)}`;
|
|
22216
|
+
}
|
|
22217
|
+
}
|
|
22218
|
+
try {
|
|
22219
|
+
report.structuredContent.platform_health = JSON.parse(await health(serveEndpoint));
|
|
22220
|
+
} catch (error2) {
|
|
22221
|
+
report.structuredContent.platform_health_unavailable = `freellama serve unreachable: ${error2 instanceof Error ? error2.message : String(error2)}`;
|
|
22222
|
+
}
|
|
22223
|
+
const full = { ...report.structuredContent };
|
|
22224
|
+
delete full.ollama_env_config;
|
|
22225
|
+
if ((view ?? "summary") === "full") return structuredResult(full);
|
|
22226
|
+
const running = full.running?.models ?? [];
|
|
22227
|
+
const compact = {
|
|
22228
|
+
status: "ok",
|
|
22229
|
+
summary: `Ollama ${String(full.version?.version ?? "unknown")}; ${running.length} resident model(s).`,
|
|
22230
|
+
endpoint: full.endpoint,
|
|
22231
|
+
ollama: { endpoint: full.endpoint, version: full.version, resident_model_count: running.length },
|
|
22232
|
+
machine: full.machine,
|
|
22233
|
+
local_conservative_config_posture: full.local_conservative_config_posture,
|
|
22234
|
+
host_runtime_signals: full.host_runtime_signals,
|
|
22235
|
+
evidence: { runtime: "ollama_api_version_and_ps", configuration: full.ollama_env_config_source },
|
|
22236
|
+
next: 'Use models{view:"installed"} to select a local model; view:"full" has Ollama configuration and admission.'
|
|
22237
|
+
};
|
|
22238
|
+
return structuredResult(compact);
|
|
22239
|
+
} catch (error2) {
|
|
22240
|
+
return errorResult(error2);
|
|
22241
|
+
}
|
|
22242
|
+
}
|
|
22243
|
+
);
|
|
22244
|
+
}
|
|
22245
|
+
|
|
22246
|
+
// src/serve-client.ts
|
|
22247
|
+
var createSession2 = withServe(createSession);
|
|
22248
|
+
var deleteSession2 = withServe(deleteSession);
|
|
22249
|
+
var killSession2 = withServe(killSession);
|
|
22250
|
+
var listModels2 = withServe(listModels);
|
|
22251
|
+
var route2 = withServe(route);
|
|
22252
|
+
var runTaskRequest2 = withServe(runTaskRequest);
|
|
22253
|
+
var runTaskBatchRequest2 = withServe(runTaskBatchRequest);
|
|
22254
|
+
var warmModelRequest2 = withServe(warmModelRequest);
|
|
22255
|
+
var listTaskJobs2 = withServe(listTaskJobs);
|
|
22256
|
+
var getTaskJob2 = withServe(getTaskJob);
|
|
22257
|
+
var cancelTaskJob2 = withServe(cancelTaskJob);
|
|
22258
|
+
var removeTaskJob2 = withServe(removeTaskJob);
|
|
22259
|
+
var createScope2 = withServe(createScope);
|
|
22260
|
+
var getScope2 = withServe(getScope);
|
|
22261
|
+
var forkScope2 = withServe(forkScope);
|
|
22262
|
+
var deleteScope2 = withServe(deleteScope);
|
|
22263
|
+
|
|
22264
|
+
// src/tools/session.ts
|
|
22265
|
+
var sessionResultSchema = external_exports.object({
|
|
22266
|
+
session_id: external_exports.string().optional(),
|
|
22267
|
+
deleted: external_exports.boolean().optional(),
|
|
22268
|
+
killed: external_exports.boolean().optional(),
|
|
22269
|
+
runner_stop_confirmed: external_exports.boolean().optional()
|
|
22270
|
+
}).passthrough();
|
|
22271
|
+
function registerSessionTool(server2) {
|
|
22272
|
+
server2.registerTool(
|
|
22273
|
+
"session",
|
|
22274
|
+
{
|
|
22275
|
+
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.",
|
|
22276
|
+
inputSchema: external_exports.object({
|
|
22277
|
+
action: external_exports.enum(["create", "delete", "kill"]),
|
|
22278
|
+
sessionId: external_exports.string().uuid().optional(),
|
|
22279
|
+
endpoint: endpointParam
|
|
22280
|
+
}).strict(),
|
|
22281
|
+
outputSchema: sessionResultSchema,
|
|
22282
|
+
annotations: { destructiveHint: true }
|
|
22283
|
+
},
|
|
22284
|
+
async ({ action, sessionId, endpoint }) => {
|
|
22285
|
+
try {
|
|
22286
|
+
if (action === "create") {
|
|
22287
|
+
if (sessionId !== void 0) return errorResult(new Error("sessionId is only valid for action: delete or kill."));
|
|
22288
|
+
return structuredResult(JSON.parse(await createSession2(endpoint)));
|
|
22289
|
+
}
|
|
22290
|
+
if (sessionId === void 0) return errorResult(new Error(`action: ${action} requires sessionId.`));
|
|
22291
|
+
if (action === "kill") return parsedResult(await killSession2(endpoint, sessionId));
|
|
22292
|
+
await deleteSession2(endpoint, sessionId);
|
|
22293
|
+
return structuredResult({ session_id: sessionId, deleted: true });
|
|
22294
|
+
} catch (error2) {
|
|
22295
|
+
return errorResult(error2);
|
|
22296
|
+
}
|
|
22297
|
+
}
|
|
22298
|
+
);
|
|
22299
|
+
}
|
|
22300
|
+
|
|
22301
|
+
// src/paging.ts
|
|
22302
|
+
import { createHash } from "node:crypto";
|
|
22303
|
+
function pageLiveList(items, limit, cursor, identity) {
|
|
22304
|
+
const pageSize = limit ?? 20;
|
|
22305
|
+
const fingerprint = createHash("sha256").update(items.map(identity).join("\n")).digest("base64url").slice(0, 16);
|
|
22306
|
+
let offset = 0;
|
|
22307
|
+
if (cursor) {
|
|
22308
|
+
try {
|
|
22309
|
+
const parsed = JSON.parse(Buffer.from(cursor, "base64url").toString("utf8"));
|
|
22310
|
+
if (!Number.isInteger(parsed.offset) || parsed.offset < 0 || parsed.fingerprint !== fingerprint) {
|
|
22311
|
+
throw new Error("invalid or stale cursor");
|
|
22312
|
+
}
|
|
22313
|
+
offset = parsed.offset;
|
|
22314
|
+
} catch {
|
|
22315
|
+
throw new Error("`cursor` is invalid or the model list changed; restart without cursor.");
|
|
22316
|
+
}
|
|
22317
|
+
}
|
|
22318
|
+
const page = items.slice(offset, offset + pageSize);
|
|
22319
|
+
const nextOffset = offset + page.length;
|
|
22320
|
+
return {
|
|
22321
|
+
items: page,
|
|
22322
|
+
returned: page.length,
|
|
22323
|
+
total: items.length,
|
|
22324
|
+
next_cursor: nextOffset < items.length ? Buffer.from(JSON.stringify({ offset: nextOffset, fingerprint }), "utf8").toString("base64url") : null
|
|
22325
|
+
};
|
|
22326
|
+
}
|
|
22327
|
+
|
|
22328
|
+
// src/model-search.ts
|
|
22329
|
+
function decodeHtml(text) {
|
|
22330
|
+
const entities = { amp: "&", lt: "<", gt: ">", quot: '"', apos: "'", nbsp: " ", middot: "\xB7", bull: "\u2022" };
|
|
22331
|
+
return text.replace(/&(#x[\da-f]+|#\d+|[a-z]+);/gi, (raw, entity) => {
|
|
22332
|
+
if (!entity.startsWith("#")) return entities[entity.toLowerCase()] ?? raw;
|
|
22333
|
+
const code = entity[1].toLowerCase() === "x" ? Number.parseInt(entity.slice(2), 16) : Number(entity.slice(1));
|
|
22334
|
+
return code > 0 && code <= 1114111 && !(code >= 55296 && code <= 57343) ? String.fromCodePoint(code) : raw;
|
|
22335
|
+
});
|
|
22336
|
+
}
|
|
22337
|
+
function attribute(tag, name) {
|
|
22338
|
+
return tag.match(new RegExp(`(?:^|\\s)${name}\\s*=\\s*(["'])([\\s\\S]*?)\\1`, "i"))?.[2];
|
|
22339
|
+
}
|
|
22340
|
+
function htmlText(html) {
|
|
22341
|
+
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");
|
|
22342
|
+
}
|
|
22343
|
+
function divContent(html, id) {
|
|
22187
22344
|
const tags = /<\/?div\b[^>]*>/gi;
|
|
22188
22345
|
let start;
|
|
22189
22346
|
let depth = 0;
|
|
@@ -22227,7 +22384,8 @@ function parseModelSearch(html) {
|
|
|
22227
22384
|
const name = link?.[1] ?? link?.[2];
|
|
22228
22385
|
if (!name) continue;
|
|
22229
22386
|
if (!/<p\b[^>]*class="max-w-lg/.test(block)) continue;
|
|
22230
|
-
const
|
|
22387
|
+
const rawDescription = block.match(/<p class="max-w-lg[^"]*">([\s\S]*?)<\/p>/)?.[1];
|
|
22388
|
+
const description = rawDescription === void 0 ? "" : decodeHtml(rawDescription.replace(/<[^>]+>/g, "")).replace(/\s+/g, " ").trim();
|
|
22231
22389
|
const capabilities = [...block.matchAll(/text-(?:indigo-600|cyan-500)[^>]*>([a-z]+)<\/span>/g)].map(
|
|
22232
22390
|
(m) => m[1]
|
|
22233
22391
|
);
|
|
@@ -22240,8 +22398,7 @@ function parseModelSearch(html) {
|
|
|
22240
22398
|
capabilities,
|
|
22241
22399
|
pulls: stat("Pulls"),
|
|
22242
22400
|
tags: stat("Tag"),
|
|
22243
|
-
cloudAvailable: capabilities.includes("cloud")
|
|
22244
|
-
cloudOnly: null
|
|
22401
|
+
cloudAvailable: capabilities.includes("cloud")
|
|
22245
22402
|
});
|
|
22246
22403
|
}
|
|
22247
22404
|
return results;
|
|
@@ -22284,6 +22441,10 @@ var CACHE_TTL_MS = 60 * 60 * 1e3;
|
|
|
22284
22441
|
var FAILURE_TTL_MS = 60 * 1e3;
|
|
22285
22442
|
var MAX_PAGE_BYTES = 2 * 1024 * 1024;
|
|
22286
22443
|
var MAX_README_CHARS = 64 * 1024;
|
|
22444
|
+
var errorMessage = (error2) => error2 instanceof Error ? error2.message : String(error2);
|
|
22445
|
+
function isRemoteModel(entry, name) {
|
|
22446
|
+
return Boolean(entry.remote_host || entry.remote_model) || /-cloud$/i.test(name);
|
|
22447
|
+
}
|
|
22287
22448
|
function libraryReference(model) {
|
|
22288
22449
|
const match = model.match(/^(?:library\/)?([a-zA-Z0-9][\w.-]*(?:\/[a-zA-Z0-9][\w.-]*)?)(?::([\w.-]+))?$/);
|
|
22289
22450
|
if (!match || match[1].split("/").some((part) => part === "." || part === "..")) return null;
|
|
@@ -22402,7 +22563,7 @@ var OllamaLibraryClient = class {
|
|
|
22402
22563
|
} catch (error2) {
|
|
22403
22564
|
result = {
|
|
22404
22565
|
status: error2 instanceof LibraryFetchError ? error2.status : "unavailable",
|
|
22405
|
-
message: error2 instanceof LibraryFetchError ? error2.message :
|
|
22566
|
+
message: error2 instanceof LibraryFetchError ? error2.message : `Ollama library lookup failed or timed out (${errorMessage(error2)}). Local metadata remains available.`
|
|
22406
22567
|
};
|
|
22407
22568
|
}
|
|
22408
22569
|
const now = (this.options.now ?? Date.now)();
|
|
@@ -22481,7 +22642,7 @@ function modelKnowledge(model, data, record3, includeReadme = false, supportedFe
|
|
|
22481
22642
|
quality: "metadata_only",
|
|
22482
22643
|
note: "Supported features and advertised uses do not establish task quality. Keep exact-model benchmark and policy evidence separate."
|
|
22483
22644
|
};
|
|
22484
|
-
if (data
|
|
22645
|
+
if (isRemoteModel(data, model)) {
|
|
22485
22646
|
return { ...local, localModel: false, library: { status: "remote_model", message: "This Ollama entry points to a remote model." } };
|
|
22486
22647
|
}
|
|
22487
22648
|
if (!record3) return { ...local, localModel: true, library: { status: "not_requested" } };
|
|
@@ -22531,11 +22692,11 @@ async function enrichInstalledModels(models, client, includeLibrary, loadTags) {
|
|
|
22531
22692
|
local = { ...model, ...entry };
|
|
22532
22693
|
if (Array.isArray(entry.capabilities)) featureSource = "ollama_api";
|
|
22533
22694
|
}
|
|
22534
|
-
} catch {
|
|
22535
|
-
localError =
|
|
22695
|
+
} catch (error2) {
|
|
22696
|
+
localError = `The selected Ollama backend's inventory could not be verified (${errorMessage(error2)}); public guidance was withheld.`;
|
|
22536
22697
|
}
|
|
22537
22698
|
}
|
|
22538
|
-
const record3 = includeLibrary && !localError && !local
|
|
22699
|
+
const record3 = includeLibrary && !localError && !isRemoteModel(local, name) ? await client.lookup(name) : void 0;
|
|
22539
22700
|
const knowledge = modelKnowledge(name, local, record3, false, featureSource);
|
|
22540
22701
|
results[index] = { ...model, knowledge: localError ? { ...knowledge, library: { status: "unverified", message: localError } } : knowledge };
|
|
22541
22702
|
}
|
|
@@ -22543,229 +22704,277 @@ async function enrichInstalledModels(models, client, includeLibrary, loadTags) {
|
|
|
22543
22704
|
return results;
|
|
22544
22705
|
}
|
|
22545
22706
|
|
|
22546
|
-
// src/
|
|
22547
|
-
|
|
22548
|
-
|
|
22549
|
-
|
|
22550
|
-
|
|
22551
|
-
|
|
22552
|
-
|
|
22553
|
-
|
|
22554
|
-
|
|
22555
|
-
|
|
22556
|
-
|
|
22557
|
-
|
|
22558
|
-
|
|
22559
|
-
|
|
22560
|
-
|
|
22561
|
-
|
|
22562
|
-
|
|
22563
|
-
|
|
22707
|
+
// src/tools/models.ts
|
|
22708
|
+
var modelsResultSchema = external_exports.object({
|
|
22709
|
+
models: external_exports.array(external_exports.record(external_exports.unknown())).optional(),
|
|
22710
|
+
tags: external_exports.array(external_exports.record(external_exports.unknown())).optional(),
|
|
22711
|
+
page: external_exports.unknown().optional(),
|
|
22712
|
+
knowledge: external_exports.unknown().optional(),
|
|
22713
|
+
library: external_exports.unknown().optional()
|
|
22714
|
+
}).passthrough();
|
|
22715
|
+
var ollamaLibrary = new OllamaLibraryClient();
|
|
22716
|
+
var HOST_MEMORY_FIT_FRACTION = 0.6;
|
|
22717
|
+
async function libraryLookup({
|
|
22718
|
+
model,
|
|
22719
|
+
query,
|
|
22720
|
+
capabilities,
|
|
22721
|
+
order,
|
|
22722
|
+
limit,
|
|
22723
|
+
cursor,
|
|
22724
|
+
includeReadme,
|
|
22725
|
+
endpoint,
|
|
22726
|
+
ollamaEndpoint
|
|
22727
|
+
}) {
|
|
22728
|
+
const localState = async () => {
|
|
22729
|
+
let installed = null;
|
|
22730
|
+
let installedUnavailable;
|
|
22731
|
+
let memoryBytes = null;
|
|
22732
|
+
try {
|
|
22733
|
+
const tags = await ollamaFetch(ollamaEndpoint, "/api/tags");
|
|
22734
|
+
installed = new Set((tags.models ?? []).map((m) => m.name ?? ""));
|
|
22735
|
+
} catch (error2) {
|
|
22736
|
+
installedUnavailable = `Local inventory could not be read (${error2 instanceof Error ? error2.message : String(error2)}); installed is null.`;
|
|
22564
22737
|
}
|
|
22565
|
-
};
|
|
22566
|
-
}
|
|
22567
|
-
var MODEL_EVIDENCE = (() => {
|
|
22568
|
-
const configured = process.env.FREELLAMA_MCP_MODEL_EVIDENCE;
|
|
22569
|
-
const candidates = [
|
|
22570
|
-
configured,
|
|
22571
|
-
path4.join(REPO_ROOT, "benchmark/evidence/model-evidence.json")
|
|
22572
|
-
].filter((x) => Boolean(x));
|
|
22573
|
-
for (const file of candidates) {
|
|
22574
22738
|
try {
|
|
22575
|
-
|
|
22576
|
-
|
|
22577
|
-
return parsed.models ?? {};
|
|
22739
|
+
const profile = JSON.parse(await machine(endpoint));
|
|
22740
|
+
memoryBytes = profile.memory_bytes ?? profile.unified_memory_bytes ?? null;
|
|
22578
22741
|
} catch {
|
|
22579
22742
|
}
|
|
22743
|
+
return { installed, installedUnavailable, memoryBytes };
|
|
22744
|
+
};
|
|
22745
|
+
if (!model && cursor !== void 0) {
|
|
22746
|
+
return errorResult(new Error("Library step 1 has no `cursor`; it pages step-2 tags. Use `limit` for search, or pass `model`."));
|
|
22580
22747
|
}
|
|
22581
|
-
|
|
22582
|
-
|
|
22583
|
-
|
|
22584
|
-
|
|
22585
|
-
|
|
22586
|
-
|
|
22587
|
-
|
|
22588
|
-
|
|
22589
|
-
|
|
22590
|
-
|
|
22591
|
-
|
|
22592
|
-
|
|
22593
|
-
|
|
22594
|
-
|
|
22595
|
-
|
|
22596
|
-
|
|
22597
|
-
|
|
22598
|
-
|
|
22599
|
-
|
|
22600
|
-
|
|
22601
|
-
|
|
22602
|
-
|
|
22603
|
-
|
|
22604
|
-
|
|
22605
|
-
|
|
22606
|
-
|
|
22607
|
-
|
|
22608
|
-
|
|
22609
|
-
|
|
22610
|
-
|
|
22611
|
-
|
|
22612
|
-
|
|
22613
|
-
|
|
22614
|
-
|
|
22615
|
-
|
|
22616
|
-
|
|
22617
|
-
|
|
22618
|
-
|
|
22619
|
-
|
|
22620
|
-
|
|
22621
|
-
|
|
22622
|
-
|
|
22623
|
-
|
|
22624
|
-
|
|
22625
|
-
|
|
22748
|
+
try {
|
|
22749
|
+
if (model) {
|
|
22750
|
+
const ref = libraryReference(model);
|
|
22751
|
+
if (!ref) throw new Error("Use an exact Ollama family or namespace/model name, not a URL or path.");
|
|
22752
|
+
const { family } = ref;
|
|
22753
|
+
const library = await ollamaLibrary.lookup(model);
|
|
22754
|
+
if (library.status !== "available") return structuredResult({
|
|
22755
|
+
family,
|
|
22756
|
+
sourcePage: ref.url,
|
|
22757
|
+
tags: [],
|
|
22758
|
+
tagsUnavailable: library.message,
|
|
22759
|
+
library,
|
|
22760
|
+
recommendation: null
|
|
22761
|
+
});
|
|
22762
|
+
const tags = library.tags;
|
|
22763
|
+
const { installed: installed2, installedUnavailable: installedUnavailable2, memoryBytes } = await localState();
|
|
22764
|
+
const budget = memoryBytes ? memoryBytes * HOST_MEMORY_FIT_FRACTION : null;
|
|
22765
|
+
const annotated = tags.map((t) => ({
|
|
22766
|
+
...t,
|
|
22767
|
+
installed: installed2 ? installed2.has(t.tag) : null,
|
|
22768
|
+
fitsInMemory: budget && t.sizeBytes ? t.sizeBytes <= budget : null,
|
|
22769
|
+
fitScope: "host_memory_budget_only"
|
|
22770
|
+
}));
|
|
22771
|
+
const runnable = annotated.filter((t) => t.fitsInMemory === true && !t.cloud);
|
|
22772
|
+
const isEmbeddingFamily = library.card.capabilities.includes("embedding");
|
|
22773
|
+
const sized = runnable.filter((t) => t.sizeBytes);
|
|
22774
|
+
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;
|
|
22775
|
+
const tagPage = pageLiveList(annotated, limit, cursor, (tag) => tag.tag);
|
|
22776
|
+
return structuredResult({
|
|
22777
|
+
family,
|
|
22778
|
+
sourcePage: ref.url,
|
|
22779
|
+
library: {
|
|
22780
|
+
status: library.status,
|
|
22781
|
+
sourceUrl: library.sourceUrl,
|
|
22782
|
+
tagsSourceUrl: library.tagsSourceUrl,
|
|
22783
|
+
fetchedAt: library.fetchedAt,
|
|
22784
|
+
expiresAt: library.expiresAt,
|
|
22785
|
+
cached: library.cached,
|
|
22786
|
+
...publicModelGuidance(library, void 0, includeReadme)
|
|
22787
|
+
},
|
|
22788
|
+
...installedUnavailable2 ? { installedUnavailable: installedUnavailable2 } : {},
|
|
22789
|
+
machineMemoryBytes: memoryBytes,
|
|
22790
|
+
fitBudgetBytes: budget,
|
|
22791
|
+
tags: tagPage.items,
|
|
22792
|
+
page: { returned: tagPage.returned, total: tagPage.total, next_cursor: tagPage.next_cursor },
|
|
22793
|
+
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.",
|
|
22794
|
+
recommendation: best ? libraryTrialCandidate(best.tag, isEmbeddingFamily) : null
|
|
22795
|
+
});
|
|
22796
|
+
}
|
|
22797
|
+
const params = new URLSearchParams();
|
|
22798
|
+
for (const c of capabilities ?? []) params.append("c", c);
|
|
22799
|
+
if (query) params.set("q", query);
|
|
22800
|
+
if (order === "newest") params.set("o", "newest");
|
|
22801
|
+
const search = await ollamaLibrary.search(params);
|
|
22802
|
+
const url = search.url;
|
|
22803
|
+
const parsed = search.models.slice(0, limit ?? 10);
|
|
22804
|
+
const { installed, installedUnavailable } = await localState();
|
|
22805
|
+
const installedFamilies = installed ? new Set([...installed].map((n) => n.split(":")[0])) : null;
|
|
22806
|
+
return structuredResult({
|
|
22807
|
+
query: url,
|
|
22808
|
+
fetchedAt: search.fetchedAt,
|
|
22809
|
+
...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." } : {},
|
|
22810
|
+
order: order ?? "popular",
|
|
22811
|
+
count: parsed.length,
|
|
22812
|
+
...installedUnavailable ? { installedUnavailable } : {},
|
|
22813
|
+
nextStep: 'Not pullable yet. Call again with view:"library", model:"<name>" to get tags, sizes and memory fit.',
|
|
22814
|
+
models: parsed.map((m) => ({ ...m, installed: installedFamilies ? installedFamilies.has(m.name) : null }))
|
|
22815
|
+
});
|
|
22816
|
+
} catch (error2) {
|
|
22817
|
+
return errorResult(error2);
|
|
22626
22818
|
}
|
|
22627
|
-
if (evidenceCount > 5) {
|
|
22628
|
-
return {
|
|
22629
|
-
recommendation: "verify",
|
|
22630
|
-
grounded: true,
|
|
22631
|
-
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.`,
|
|
22632
|
-
measuredBaseRate: `${model}: 98.9% on grounded lookups, measured within a 1-5 file scope`
|
|
22633
|
-
};
|
|
22634
|
-
}
|
|
22635
|
-
return {
|
|
22636
|
-
recommendation: "accept",
|
|
22637
|
-
grounded: true,
|
|
22638
|
-
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}).`,
|
|
22639
|
-
measuredBaseRate: `${model}: 98.9% on grounded lookups (100+ questions)`
|
|
22640
|
-
};
|
|
22641
22819
|
}
|
|
22642
|
-
|
|
22643
|
-
|
|
22644
|
-
|
|
22645
|
-
|
|
22646
|
-
|
|
22647
|
-
|
|
22648
|
-
|
|
22649
|
-
|
|
22650
|
-
|
|
22651
|
-
|
|
22652
|
-
|
|
22653
|
-
|
|
22654
|
-
|
|
22655
|
-
|
|
22656
|
-
|
|
22657
|
-
|
|
22658
|
-
|
|
22659
|
-
|
|
22660
|
-
|
|
22661
|
-
|
|
22662
|
-
|
|
22663
|
-
route_defaults: defaults === void 0 ? void 0 : {
|
|
22664
|
-
task: defaults.task === void 0 ? void 0 : canonicalTaskKind(defaults.task),
|
|
22665
|
-
objective: defaults.objective,
|
|
22666
|
-
model: defaults.model,
|
|
22667
|
-
context_tokens: defaults.contextTokens,
|
|
22668
|
-
execution_preference: defaults.executionPreference,
|
|
22669
|
-
min_placement_evidence: defaults.minPlacementEvidence,
|
|
22670
|
-
min_confidence: defaults.minConfidence,
|
|
22671
|
-
required_capabilities: defaults.requiredCapabilities
|
|
22820
|
+
function registerModelsTool(server2) {
|
|
22821
|
+
server2.registerTool(
|
|
22822
|
+
"models",
|
|
22823
|
+
{
|
|
22824
|
+
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.",
|
|
22825
|
+
inputSchema: external_exports.object({
|
|
22826
|
+
view: external_exports.enum(["installed", "resident", "detail", "raw", "library"]).optional(),
|
|
22827
|
+
model: external_exports.string().min(1).optional(),
|
|
22828
|
+
includeVerbose: external_exports.boolean().optional(),
|
|
22829
|
+
includeLibrary: external_exports.boolean().optional(),
|
|
22830
|
+
includeReadme: external_exports.boolean().optional(),
|
|
22831
|
+
query: external_exports.string().min(1).optional(),
|
|
22832
|
+
capabilities: external_exports.array(external_exports.enum(["vision", "tools", "thinking", "embedding", "cloud"])).min(1).max(5).optional(),
|
|
22833
|
+
order: external_exports.enum(["popular", "newest"]).optional(),
|
|
22834
|
+
limit: external_exports.number().int().positive().max(50).optional(),
|
|
22835
|
+
cursor: external_exports.string().min(1).optional(),
|
|
22836
|
+
endpoint: endpointParam,
|
|
22837
|
+
ollamaEndpoint: ollamaEndpointParam
|
|
22838
|
+
}).strict(),
|
|
22839
|
+
outputSchema: modelsResultSchema,
|
|
22840
|
+
annotations: { readOnlyHint: true }
|
|
22672
22841
|
},
|
|
22673
|
-
|
|
22674
|
-
|
|
22675
|
-
|
|
22676
|
-
|
|
22677
|
-
|
|
22842
|
+
async ({ view, model, includeVerbose, includeLibrary, includeReadme, query, capabilities, order, limit, cursor, endpoint, ollamaEndpoint }) => {
|
|
22843
|
+
try {
|
|
22844
|
+
const selectedView = view ?? "installed";
|
|
22845
|
+
if (includeLibrary !== void 0 && selectedView !== "installed" && selectedView !== "detail") {
|
|
22846
|
+
return errorResult(new Error('`includeLibrary` is valid only for views "installed" and "detail".'));
|
|
22847
|
+
}
|
|
22848
|
+
if (includeReadme !== void 0 && !(selectedView === "library" && model || selectedView === "detail" && includeLibrary === true)) {
|
|
22849
|
+
return errorResult(new Error('`includeReadme` needs library step 2, or view "detail" with `includeLibrary:true`.'));
|
|
22850
|
+
}
|
|
22851
|
+
if (selectedView !== "library" && [query, capabilities, order].some((value) => value !== void 0)) {
|
|
22852
|
+
return errorResult(new Error(`view "${selectedView}" does not accept library search fields.`));
|
|
22853
|
+
}
|
|
22854
|
+
if (selectedView !== "library" && selectedView !== "raw" && [limit, cursor].some((value) => value !== void 0)) {
|
|
22855
|
+
return errorResult(new Error(`view "${selectedView}" accepts neither pagination nor library search fields.`));
|
|
22856
|
+
}
|
|
22857
|
+
if (selectedView !== "detail" && includeVerbose !== void 0) {
|
|
22858
|
+
return errorResult(new Error('`includeVerbose` is valid only for view "detail".'));
|
|
22859
|
+
}
|
|
22860
|
+
if (selectedView !== "detail" && selectedView !== "library" && model !== void 0) {
|
|
22861
|
+
return errorResult(new Error('`model` is valid only for views "detail" and "library".'));
|
|
22862
|
+
}
|
|
22863
|
+
if (selectedView === "library" && model && [query, capabilities, order].some((value) => value !== void 0)) {
|
|
22864
|
+
return errorResult(
|
|
22865
|
+
new Error("Library step 2 accepts only `model` plus endpoint overrides; omit step-1 search fields.")
|
|
22866
|
+
);
|
|
22867
|
+
}
|
|
22868
|
+
switch (selectedView) {
|
|
22869
|
+
case "raw": {
|
|
22870
|
+
const raw = await ollamaFetch(ollamaEndpoint, "/api/tags");
|
|
22871
|
+
const models = Array.isArray(raw.models) ? raw.models : [];
|
|
22872
|
+
const page = pageLiveList(models, limit, cursor, (model2) => String(model2.name ?? JSON.stringify(model2)));
|
|
22873
|
+
return structuredResult({ ...raw, models: page.items, page: { returned: page.returned, total: page.total, next_cursor: page.next_cursor } });
|
|
22874
|
+
}
|
|
22875
|
+
case "resident": {
|
|
22876
|
+
const managed = parsedResult(await listModels2(endpoint));
|
|
22877
|
+
if (!isStructuredSuccess(managed)) return managed;
|
|
22878
|
+
const data = managed.structuredContent;
|
|
22879
|
+
const models = (data.models ?? []).filter((entry) => entry.resident === true).map((entry) => {
|
|
22880
|
+
const { resident_view: view2, ...rest } = entry;
|
|
22881
|
+
if (!view2) return rest;
|
|
22882
|
+
return {
|
|
22883
|
+
...rest,
|
|
22884
|
+
placement: {
|
|
22885
|
+
gpu_percent: view2.gpu_percent,
|
|
22886
|
+
assigned: view2.assigned,
|
|
22887
|
+
processor: view2.processor_label,
|
|
22888
|
+
...view2.warning !== void 0 ? { warning: view2.warning } : {}
|
|
22889
|
+
}
|
|
22890
|
+
};
|
|
22891
|
+
});
|
|
22892
|
+
return structuredResult({ ...data, models });
|
|
22893
|
+
}
|
|
22894
|
+
case "detail": {
|
|
22895
|
+
if (!model) {
|
|
22896
|
+
return errorResult(new Error('view "detail" needs `model` set to an installed tag.'));
|
|
22897
|
+
}
|
|
22898
|
+
const data = await ollamaFetch(ollamaEndpoint, "/api/show", {
|
|
22899
|
+
method: "POST",
|
|
22900
|
+
body: { model }
|
|
22901
|
+
});
|
|
22902
|
+
let local = data;
|
|
22903
|
+
if (includeLibrary && !isRemoteModel(data, model)) {
|
|
22904
|
+
try {
|
|
22905
|
+
const inventory = await ollamaFetch(ollamaEndpoint, "/api/tags");
|
|
22906
|
+
const canonical = libraryReference(model)?.tag;
|
|
22907
|
+
const entry = canonical && inventory.models?.find((entry2) => libraryReference(String(entry2.name ?? entry2.model))?.tag === canonical);
|
|
22908
|
+
if (entry) local = { ...entry, ...data, digest: entry.digest };
|
|
22909
|
+
} catch {
|
|
22910
|
+
}
|
|
22911
|
+
}
|
|
22912
|
+
const library = includeLibrary && !isRemoteModel(local, model) ? await ollamaLibrary.lookup(model) : void 0;
|
|
22913
|
+
const { license, modelfile, ...rest } = data;
|
|
22914
|
+
const modelInfo = rest.model_info ?? {};
|
|
22915
|
+
const contextEntry = Object.entries(modelInfo).find(
|
|
22916
|
+
([key, value]) => key.endsWith(".context_length") && typeof value === "number"
|
|
22917
|
+
);
|
|
22918
|
+
return structuredResult({
|
|
22919
|
+
...rest,
|
|
22920
|
+
max_context_length: contextEntry?.[1] ?? null,
|
|
22921
|
+
knowledge: modelKnowledge(model, local, library, includeReadme),
|
|
22922
|
+
...includeVerbose ? { license, modelfile } : {}
|
|
22923
|
+
});
|
|
22924
|
+
}
|
|
22925
|
+
case "library":
|
|
22926
|
+
return await libraryLookup({ model, query, capabilities, order, limit, cursor, includeReadme, endpoint, ollamaEndpoint });
|
|
22927
|
+
default: {
|
|
22928
|
+
const result = parsedResult(await listModels2(endpoint));
|
|
22929
|
+
if (!isStructuredSuccess(result)) return result;
|
|
22930
|
+
const data = result.structuredContent;
|
|
22931
|
+
return structuredResult({ ...data, models: await enrichInstalledModels(
|
|
22932
|
+
data.models ?? [],
|
|
22933
|
+
ollamaLibrary,
|
|
22934
|
+
includeLibrary ?? false,
|
|
22935
|
+
async (upstream) => (await ollamaFetch(upstream ?? ollamaEndpoint, "/api/tags")).models ?? []
|
|
22936
|
+
) });
|
|
22937
|
+
}
|
|
22938
|
+
}
|
|
22939
|
+
} catch (error2) {
|
|
22940
|
+
return errorResult(error2);
|
|
22941
|
+
}
|
|
22678
22942
|
}
|
|
22679
|
-
|
|
22943
|
+
);
|
|
22680
22944
|
}
|
|
22681
|
-
|
|
22682
|
-
|
|
22683
|
-
|
|
22684
|
-
const forkScope2 = withServe(forkScope);
|
|
22685
|
-
const deleteScope2 = withServe(deleteScope);
|
|
22686
|
-
const warmModel = withServe(warmModelRequest);
|
|
22945
|
+
|
|
22946
|
+
// src/tools/scope.ts
|
|
22947
|
+
function registerScopeTool(server2) {
|
|
22687
22948
|
server2.registerTool("scope", {
|
|
22688
|
-
description: "Use when:
|
|
22949
|
+
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.",
|
|
22689
22950
|
inputSchema: external_exports.object({
|
|
22690
22951
|
action: external_exports.enum(["create", "get", "fork", "delete"]),
|
|
22691
22952
|
endpoint: endpointParam,
|
|
22692
22953
|
scopeId: external_exports.string().uuid().optional(),
|
|
22693
22954
|
revision: external_exports.number().int().nonnegative().optional(),
|
|
22694
22955
|
messages: messagesParam.unwrap().min(0).optional(),
|
|
22695
|
-
routeDefaults: routeDefaults.optional(),
|
|
22696
|
-
limits: limits.optional(),
|
|
22697
22956
|
includeMessages: external_exports.boolean().optional()
|
|
22698
22957
|
}).strict(),
|
|
22699
22958
|
outputSchema: objectResultSchema,
|
|
22700
22959
|
annotations: { destructiveHint: true }
|
|
22701
|
-
}, async ({ action, endpoint, scopeId, revision, messages,
|
|
22960
|
+
}, async ({ action, endpoint, scopeId, revision, messages, includeMessages }) => {
|
|
22702
22961
|
try {
|
|
22703
22962
|
if (action === "create" !== (scopeId === void 0)) throw new Error("scopeId is required for get/fork/delete, forbidden for create.");
|
|
22704
22963
|
if (action === "fork" !== (revision !== void 0)) throw new Error("revision is required only for fork.");
|
|
22705
22964
|
if (includeMessages !== void 0 && action !== "get") throw new Error("includeMessages is valid only for get.");
|
|
22706
22965
|
if (messages !== void 0 && action !== "create") throw new Error("messages is valid only for create.");
|
|
22707
|
-
if ((defaults !== void 0 || bounds !== void 0) && action !== "create" && action !== "fork") throw new Error("routeDefaults and limits are valid only for create/fork.");
|
|
22708
22966
|
if (action === "delete") {
|
|
22709
22967
|
await deleteScope2(endpoint, scopeId);
|
|
22710
22968
|
return structuredResult({ deleted: true, scope_id: scopeId });
|
|
22711
22969
|
}
|
|
22712
|
-
return parsedResult(action === "create" ? await createScope2(endpoint,
|
|
22713
|
-
} catch (error2) {
|
|
22714
|
-
return errorResult(error2);
|
|
22715
|
-
}
|
|
22716
|
-
});
|
|
22717
|
-
server2.registerTool("warm_model", {
|
|
22718
|
-
description: "Use when: preload an installed model. Do not use when: generation or pull. Returns: managed residency receipt or job ID.",
|
|
22719
|
-
inputSchema: external_exports.object({
|
|
22720
|
-
endpoint: endpointParam,
|
|
22721
|
-
model: external_exports.string().min(1),
|
|
22722
|
-
task: taskParam.removeDefault().optional(),
|
|
22723
|
-
contextTokens: external_exports.number().int().positive().optional(),
|
|
22724
|
-
executionPreference: executionPreferenceParam,
|
|
22725
|
-
minPlacementEvidence: minPlacementEvidenceParam,
|
|
22726
|
-
priority: external_exports.enum(["interactive", "normal", "background"]).optional(),
|
|
22727
|
-
keepAlive: external_exports.string().min(1).optional().describe("Omitted = adaptive finite TTL."),
|
|
22728
|
-
maxWaitSeconds: external_exports.number().int().positive().optional(),
|
|
22729
|
-
timeoutSeconds: external_exports.number().int().positive().optional(),
|
|
22730
|
-
defer: external_exports.boolean().optional()
|
|
22731
|
-
}).strict(),
|
|
22732
|
-
outputSchema: objectResultSchema,
|
|
22733
|
-
annotations: { destructiveHint: false }
|
|
22734
|
-
}, async ({ endpoint, model, task, contextTokens, executionPreference, minPlacementEvidence, priority, keepAlive, maxWaitSeconds, timeoutSeconds, defer }) => {
|
|
22735
|
-
try {
|
|
22736
|
-
if (task === "embedding") throw new Error("warm_model supports chat models; embedding warming is unsupported.");
|
|
22737
|
-
return parsedResult(await warmModel(endpoint, {
|
|
22738
|
-
model,
|
|
22739
|
-
task: task === void 0 ? void 0 : canonicalTaskKind(task),
|
|
22740
|
-
context_tokens: contextTokens,
|
|
22741
|
-
execution_preference: executionPreference,
|
|
22742
|
-
min_placement_evidence: minPlacementEvidence,
|
|
22743
|
-
priority,
|
|
22744
|
-
keep_alive: keepAlive,
|
|
22745
|
-
max_wait_seconds: maxWaitSeconds,
|
|
22746
|
-
timeout_seconds: timeoutSeconds,
|
|
22747
|
-
defer
|
|
22748
|
-
}));
|
|
22970
|
+
return parsedResult(action === "create" ? await createScope2(endpoint, { messages }) : action === "fork" ? await forkScope2(endpoint, scopeId, { revision }) : await getScope2(endpoint, scopeId, includeMessages));
|
|
22749
22971
|
} catch (error2) {
|
|
22750
22972
|
return errorResult(error2);
|
|
22751
22973
|
}
|
|
22752
22974
|
});
|
|
22753
22975
|
}
|
|
22754
22976
|
|
|
22755
|
-
// src/
|
|
22756
|
-
var execFileAsync = promisify(execFile);
|
|
22757
|
-
var createSession2 = withServe(createSession);
|
|
22758
|
-
var deleteSession2 = withServe(deleteSession);
|
|
22759
|
-
var killSession2 = withServe(killSession);
|
|
22760
|
-
var listModels2 = withServe(listModels);
|
|
22761
|
-
var ollamaLibrary = new OllamaLibraryClient();
|
|
22762
|
-
var route2 = withServe(route);
|
|
22763
|
-
var runTaskRequest2 = withServe(runTaskRequest);
|
|
22764
|
-
var runTaskBatchRequest2 = withServe(runTaskBatchRequest);
|
|
22765
|
-
var listTaskJobs2 = withServe(listTaskJobs);
|
|
22766
|
-
var getTaskJob2 = withServe(getTaskJob);
|
|
22767
|
-
var cancelTaskJob2 = withServe(cancelTaskJob);
|
|
22768
|
-
var removeTaskJob2 = withServe(removeTaskJob);
|
|
22977
|
+
// src/telemetry.ts
|
|
22769
22978
|
var EXTERNAL_COST = configuredExternalCost();
|
|
22770
22979
|
function withTaskTelemetry(result) {
|
|
22771
22980
|
if (!isStructuredSuccess(result)) return result;
|
|
@@ -22808,55 +23017,469 @@ function withBatchTelemetry(result) {
|
|
|
22808
23017
|
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." }
|
|
22809
23018
|
}, { text: batchAnswerText(payload) });
|
|
22810
23019
|
}
|
|
22811
|
-
|
|
22812
|
-
|
|
22813
|
-
|
|
22814
|
-
|
|
22815
|
-
|
|
23020
|
+
|
|
23021
|
+
// src/tools/run-task.ts
|
|
23022
|
+
var taskResultSchema = external_exports.object({
|
|
23023
|
+
selected_model: external_exports.string().optional(),
|
|
23024
|
+
context_window_fit: external_exports.string().optional(),
|
|
23025
|
+
execution: external_exports.unknown().optional(),
|
|
23026
|
+
response: external_exports.unknown().optional(),
|
|
23027
|
+
telemetry: external_exports.unknown().optional()
|
|
23028
|
+
}).passthrough();
|
|
23029
|
+
var batchResultSchema = external_exports.object({
|
|
23030
|
+
results: external_exports.unknown().optional(),
|
|
23031
|
+
telemetry: external_exports.unknown().optional()
|
|
23032
|
+
}).passthrough();
|
|
23033
|
+
function validateTaskPayload(task, label) {
|
|
23034
|
+
const fail = (message) => label ? `${label}: ${message}` : message;
|
|
23035
|
+
if (task.scopeId === void 0 !== (task.scopeRevision === void 0)) {
|
|
23036
|
+
return fail("scopeId and scopeRevision must be supplied together.");
|
|
23037
|
+
}
|
|
23038
|
+
if (task.topLogprobs !== void 0 && task.logprobs !== true) return fail("`topLogprobs` requires `logprobs:true`.");
|
|
23039
|
+
if (task.task === "embedding") {
|
|
23040
|
+
if (task.input === void 0) return fail('task "embedding" requires `input`.');
|
|
23041
|
+
const chatControls = [
|
|
23042
|
+
task.prompt,
|
|
23043
|
+
task.systemPrompt,
|
|
23044
|
+
task.images,
|
|
23045
|
+
task.messages,
|
|
23046
|
+
task.tools,
|
|
23047
|
+
task.format,
|
|
23048
|
+
task.think,
|
|
23049
|
+
task.logprobs,
|
|
23050
|
+
task.topLogprobs
|
|
23051
|
+
];
|
|
23052
|
+
if (chatControls.some((value) => value !== void 0)) {
|
|
23053
|
+
return fail('task "embedding" accepts `input`, `options`, and routing fields; chat controls are not valid.');
|
|
23054
|
+
}
|
|
23055
|
+
return null;
|
|
23056
|
+
}
|
|
23057
|
+
if (task.input !== void 0 || task.returnEmbeddings !== void 0) {
|
|
23058
|
+
return fail('`input` and `returnEmbeddings` are valid only for task "embedding".');
|
|
23059
|
+
}
|
|
23060
|
+
if (task.prompt === void 0 && task.systemPrompt === void 0 && (task.messages === void 0 || task.messages.length === 0)) {
|
|
23061
|
+
return fail(`task "${task.task ?? "completion"}" requires \`prompt\`, \`systemPrompt\`, or \`messages\`.`);
|
|
23062
|
+
}
|
|
23063
|
+
if (task.images !== void 0 && (task.messages !== void 0 || task.prompt === void 0)) {
|
|
23064
|
+
return fail("Top-level `images` is valid only with `prompt`; put images inside messages instead.");
|
|
23065
|
+
}
|
|
23066
|
+
return null;
|
|
23067
|
+
}
|
|
23068
|
+
async function runBatch(tasks, maxParallelism, endpoint) {
|
|
23069
|
+
for (const item of tasks) {
|
|
23070
|
+
const problem = validateTaskPayload(item.task, `batch item ${item.id}`);
|
|
23071
|
+
if (problem) return errorResult(new Error(problem));
|
|
23072
|
+
}
|
|
23073
|
+
return withBatchTelemetry(parsedResult(await runTaskBatchRequest2(endpoint ?? DEFAULT_SERVE_ENDPOINT, {
|
|
23074
|
+
tasks: tasks.map(({ id, independent, task }) => ({
|
|
23075
|
+
id,
|
|
23076
|
+
independent,
|
|
23077
|
+
task: {
|
|
23078
|
+
task: task.task === void 0 ? task.scopeId === void 0 ? "completion" : void 0 : canonicalTaskKind(task.task),
|
|
23079
|
+
objective: task.objective ?? (task.scopeId === void 0 ? "balanced" : void 0),
|
|
23080
|
+
model: task.model,
|
|
23081
|
+
session_id: task.sessionId,
|
|
23082
|
+
scope_id: task.scopeId,
|
|
23083
|
+
scope_revision: task.scopeRevision,
|
|
23084
|
+
context_tokens: task.contextTokens,
|
|
23085
|
+
execution_preference: task.executionPreference,
|
|
23086
|
+
min_placement_evidence: task.minPlacementEvidence,
|
|
23087
|
+
required_capabilities: task.requiredCapabilities,
|
|
23088
|
+
priority: task.priority ?? "normal",
|
|
23089
|
+
max_wait_seconds: task.maxWaitSeconds,
|
|
23090
|
+
timeout_seconds: task.timeoutSeconds,
|
|
23091
|
+
stop_on_repetition: task.stopOnRepetition,
|
|
23092
|
+
prompt: task.prompt,
|
|
23093
|
+
images: task.images,
|
|
23094
|
+
messages: taskMessages(task),
|
|
23095
|
+
input: task.input,
|
|
23096
|
+
tools: task.tools,
|
|
23097
|
+
keep_alive: task.keepAlive,
|
|
23098
|
+
min_confidence: task.minConfidence,
|
|
23099
|
+
request_options: {
|
|
23100
|
+
format: task.format,
|
|
23101
|
+
think: task.think,
|
|
23102
|
+
options: task.options,
|
|
23103
|
+
logprobs: task.logprobs,
|
|
23104
|
+
top_logprobs: task.topLogprobs
|
|
23105
|
+
}
|
|
23106
|
+
}
|
|
23107
|
+
})),
|
|
23108
|
+
max_parallelism: maxParallelism
|
|
23109
|
+
})));
|
|
23110
|
+
}
|
|
23111
|
+
function registerRunTaskTool(server2) {
|
|
23112
|
+
server2.registerTool(
|
|
23113
|
+
"run_task",
|
|
23114
|
+
{
|
|
23115
|
+
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.",
|
|
23116
|
+
inputSchema: external_exports.object({
|
|
23117
|
+
endpoint: endpointParam,
|
|
23118
|
+
task: taskParam.removeDefault().optional().describe("caller owns prompts and output format."),
|
|
23119
|
+
objective: objectiveParam,
|
|
23120
|
+
model: external_exports.string().min(1).optional(),
|
|
23121
|
+
sessionId: external_exports.string().uuid().optional(),
|
|
23122
|
+
scopeId: external_exports.string().uuid().optional(),
|
|
23123
|
+
scopeRevision: external_exports.number().int().nonnegative().optional(),
|
|
23124
|
+
contextTokens: external_exports.number().int().positive().optional().describe("Total input + output window (num_ctx)."),
|
|
23125
|
+
executionPreference: executionPreferenceParam,
|
|
23126
|
+
minPlacementEvidence: minPlacementEvidenceParam,
|
|
23127
|
+
requiredCapabilities: requiredCapabilitiesParam,
|
|
23128
|
+
prompt: external_exports.string().min(1).optional(),
|
|
23129
|
+
systemPrompt: systemPromptParam,
|
|
23130
|
+
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]"),
|
|
23131
|
+
messages: messagesParam.describe(
|
|
23132
|
+
"Caller system prompts; no injected task instructions. Overrides prompt; extra fields preserved."
|
|
23133
|
+
),
|
|
23134
|
+
input: external_exports.union([external_exports.string().min(1), external_exports.array(external_exports.string().min(1)).min(1)]).optional(),
|
|
23135
|
+
tools: localToolsParam,
|
|
23136
|
+
keepAlive: external_exports.string().min(1).optional().describe('"0" unloads now, "-1" pins; omitted = adaptive finite TTL'),
|
|
23137
|
+
format: external_exports.union([external_exports.literal("json"), external_exports.record(external_exports.unknown())]).optional(),
|
|
23138
|
+
think: external_exports.union([external_exports.boolean(), external_exports.enum(["low", "medium", "high"])]).optional(),
|
|
23139
|
+
options: external_exports.record(external_exports.unknown()).optional().describe("num_ctx/contextTokens and num_gpu/placement are routing-owned; num_predict caps output."),
|
|
23140
|
+
logprobs: external_exports.boolean().optional(),
|
|
23141
|
+
topLogprobs: external_exports.number().int().nonnegative().optional(),
|
|
23142
|
+
minConfidence: minConfidenceParam,
|
|
23143
|
+
priority: external_exports.enum(["interactive", "normal", "background"]).optional(),
|
|
23144
|
+
maxWaitSeconds: external_exports.number().int().positive().optional(),
|
|
23145
|
+
timeoutSeconds: external_exports.number().int().positive().optional(),
|
|
23146
|
+
stopOnRepetition: external_exports.boolean().optional().describe("Stream and stop once output repeats itself (a loop to num_predict); keeps all generated text"),
|
|
23147
|
+
defer: external_exports.boolean().optional(),
|
|
23148
|
+
returnEmbeddings: external_exports.boolean().optional(),
|
|
23149
|
+
preview: external_exports.boolean().optional().describe(
|
|
23150
|
+
"true = routing fields only; rejects payloads and runtime controls; never executes"
|
|
23151
|
+
),
|
|
23152
|
+
warm: external_exports.boolean().optional().describe("Load exact `model`; no prompt"),
|
|
23153
|
+
tasks: external_exports.array(batchItemParam).min(1).max(64).optional().describe("Batch mode: independent items; per-item fields go inside each item's `task`"),
|
|
23154
|
+
maxParallelism: external_exports.number().int().positive().max(64).optional().describe("Batch mode only")
|
|
23155
|
+
}).strict(),
|
|
23156
|
+
outputSchema: taskResultSchema.merge(batchResultSchema),
|
|
23157
|
+
annotations: { destructiveHint: false }
|
|
23158
|
+
},
|
|
23159
|
+
async ({
|
|
23160
|
+
endpoint,
|
|
23161
|
+
task,
|
|
23162
|
+
objective,
|
|
23163
|
+
model,
|
|
23164
|
+
sessionId,
|
|
23165
|
+
scopeId,
|
|
23166
|
+
scopeRevision,
|
|
23167
|
+
contextTokens,
|
|
23168
|
+
executionPreference,
|
|
23169
|
+
minPlacementEvidence,
|
|
23170
|
+
requiredCapabilities,
|
|
23171
|
+
prompt,
|
|
23172
|
+
systemPrompt,
|
|
23173
|
+
images,
|
|
23174
|
+
messages,
|
|
23175
|
+
input,
|
|
23176
|
+
tools,
|
|
23177
|
+
keepAlive,
|
|
23178
|
+
format,
|
|
23179
|
+
think,
|
|
23180
|
+
options,
|
|
23181
|
+
logprobs,
|
|
23182
|
+
topLogprobs,
|
|
23183
|
+
minConfidence,
|
|
23184
|
+
priority,
|
|
23185
|
+
maxWaitSeconds,
|
|
23186
|
+
timeoutSeconds,
|
|
23187
|
+
stopOnRepetition,
|
|
23188
|
+
defer,
|
|
23189
|
+
returnEmbeddings,
|
|
23190
|
+
preview,
|
|
23191
|
+
warm,
|
|
23192
|
+
tasks,
|
|
23193
|
+
maxParallelism
|
|
23194
|
+
}) => {
|
|
23195
|
+
try {
|
|
23196
|
+
const singleFields = {
|
|
23197
|
+
task,
|
|
23198
|
+
objective,
|
|
23199
|
+
model,
|
|
23200
|
+
sessionId,
|
|
23201
|
+
scopeId,
|
|
23202
|
+
scopeRevision,
|
|
23203
|
+
contextTokens,
|
|
23204
|
+
executionPreference,
|
|
23205
|
+
minPlacementEvidence,
|
|
23206
|
+
requiredCapabilities,
|
|
23207
|
+
prompt,
|
|
23208
|
+
systemPrompt,
|
|
23209
|
+
images,
|
|
23210
|
+
messages,
|
|
23211
|
+
input,
|
|
23212
|
+
tools,
|
|
23213
|
+
keepAlive,
|
|
23214
|
+
format,
|
|
23215
|
+
think,
|
|
23216
|
+
options,
|
|
23217
|
+
logprobs,
|
|
23218
|
+
topLogprobs,
|
|
23219
|
+
minConfidence,
|
|
23220
|
+
priority,
|
|
23221
|
+
maxWaitSeconds,
|
|
23222
|
+
timeoutSeconds,
|
|
23223
|
+
stopOnRepetition,
|
|
23224
|
+
defer,
|
|
23225
|
+
returnEmbeddings,
|
|
23226
|
+
preview,
|
|
23227
|
+
warm
|
|
23228
|
+
};
|
|
23229
|
+
if (tasks !== void 0) {
|
|
23230
|
+
const stray = Object.entries(singleFields).filter(([, value]) => value !== void 0).map(([name]) => name);
|
|
23231
|
+
const unsupported = stray.filter((name) => !BATCH_TASK_FIELDS.has(name)).map((name) => `\`${name}\``);
|
|
23232
|
+
if (unsupported.length > 0) {
|
|
23233
|
+
return errorResult(new Error(`${unsupported.join(", ")} ${unsupported.length === 1 ? "is" : "are"} not supported with \`tasks\`.`));
|
|
23234
|
+
}
|
|
23235
|
+
const movable = stray.map((name) => `\`${name}\``);
|
|
23236
|
+
if (movable.length > 0) {
|
|
23237
|
+
return errorResult(new Error(`With \`tasks\`, put ${movable.join(", ")} inside each item's \`task\`.`));
|
|
23238
|
+
}
|
|
23239
|
+
return await runBatch(tasks, maxParallelism, endpoint);
|
|
23240
|
+
}
|
|
23241
|
+
if (maxParallelism !== void 0) return errorResult(new Error("`maxParallelism` is valid only with `tasks`."));
|
|
23242
|
+
if (warm) {
|
|
23243
|
+
const allowed = /* @__PURE__ */ new Set([
|
|
23244
|
+
"warm",
|
|
23245
|
+
"model",
|
|
23246
|
+
"task",
|
|
23247
|
+
"contextTokens",
|
|
23248
|
+
"executionPreference",
|
|
23249
|
+
"minPlacementEvidence",
|
|
23250
|
+
"priority",
|
|
23251
|
+
"keepAlive",
|
|
23252
|
+
"maxWaitSeconds",
|
|
23253
|
+
"timeoutSeconds",
|
|
23254
|
+
"defer"
|
|
23255
|
+
]);
|
|
23256
|
+
const stray = Object.entries(singleFields).filter(([name, value]) => value !== void 0 && !allowed.has(name)).map(([name]) => `\`${name}\``);
|
|
23257
|
+
if (stray.length > 0) return errorResult(new Error(`\`warm:true\` loads a model without generation; remove ${stray.join(", ")}.`));
|
|
23258
|
+
if (model === void 0) return errorResult(new Error("`warm:true` requires an exact installed `model`."));
|
|
23259
|
+
if (task === "embedding") return errorResult(new Error("`warm:true` supports chat models; embedding warming is unsupported."));
|
|
23260
|
+
return parsedResult(await warmModelRequest2(endpoint, {
|
|
23261
|
+
model,
|
|
23262
|
+
task: task === void 0 ? void 0 : canonicalTaskKind(task),
|
|
23263
|
+
context_tokens: contextTokens,
|
|
23264
|
+
execution_preference: executionPreference,
|
|
23265
|
+
min_placement_evidence: minPlacementEvidence,
|
|
23266
|
+
priority,
|
|
23267
|
+
keep_alive: keepAlive,
|
|
23268
|
+
max_wait_seconds: maxWaitSeconds,
|
|
23269
|
+
timeout_seconds: timeoutSeconds,
|
|
23270
|
+
defer
|
|
23271
|
+
}));
|
|
23272
|
+
}
|
|
23273
|
+
const canonicalTask = canonicalTaskKind(task ?? "completion");
|
|
23274
|
+
if (preview) {
|
|
23275
|
+
const executionOnlyFields = [
|
|
23276
|
+
["scopeId", scopeId],
|
|
23277
|
+
["scopeRevision", scopeRevision],
|
|
23278
|
+
["prompt", prompt],
|
|
23279
|
+
["systemPrompt", systemPrompt],
|
|
23280
|
+
["images", images],
|
|
23281
|
+
["messages", messages],
|
|
23282
|
+
["input", input],
|
|
23283
|
+
["tools", tools],
|
|
23284
|
+
["keepAlive", keepAlive],
|
|
23285
|
+
["format", format],
|
|
23286
|
+
["think", think],
|
|
23287
|
+
["options", options],
|
|
23288
|
+
["logprobs", logprobs],
|
|
23289
|
+
["topLogprobs", topLogprobs],
|
|
23290
|
+
["returnEmbeddings", returnEmbeddings],
|
|
23291
|
+
["maxWaitSeconds", maxWaitSeconds],
|
|
23292
|
+
["priority", priority],
|
|
23293
|
+
["timeoutSeconds", timeoutSeconds],
|
|
23294
|
+
["stopOnRepetition", stopOnRepetition],
|
|
23295
|
+
["defer", defer]
|
|
23296
|
+
].filter(([, value]) => value !== void 0).map(([name]) => `\`${name}\``);
|
|
23297
|
+
if (executionOnlyFields.length > 0) {
|
|
23298
|
+
return errorResult(
|
|
23299
|
+
new Error(
|
|
23300
|
+
`\`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.`
|
|
23301
|
+
)
|
|
23302
|
+
);
|
|
23303
|
+
}
|
|
23304
|
+
}
|
|
23305
|
+
const effectiveRequiredCapabilities = tools === void 0 ? requiredCapabilities : withRequiredCapability(requiredCapabilities, "tools");
|
|
23306
|
+
if (preview) {
|
|
23307
|
+
return parsedResult(
|
|
23308
|
+
await route2(endpoint, canonicalTask, objective, model, sessionId, contextTokens, effectiveRequiredCapabilities, minConfidence, executionPreference, minPlacementEvidence)
|
|
23309
|
+
);
|
|
23310
|
+
}
|
|
23311
|
+
const problem = validateTaskPayload({
|
|
23312
|
+
task,
|
|
23313
|
+
scopeId,
|
|
23314
|
+
scopeRevision,
|
|
23315
|
+
prompt,
|
|
23316
|
+
systemPrompt,
|
|
23317
|
+
messages,
|
|
23318
|
+
images,
|
|
23319
|
+
input,
|
|
23320
|
+
tools,
|
|
23321
|
+
format,
|
|
23322
|
+
think,
|
|
23323
|
+
logprobs,
|
|
23324
|
+
topLogprobs,
|
|
23325
|
+
returnEmbeddings
|
|
23326
|
+
});
|
|
23327
|
+
if (problem) return errorResult(new Error(problem));
|
|
23328
|
+
if (minConfidence && defer && scopeId === void 0) {
|
|
23329
|
+
const decision = parsedResult(
|
|
23330
|
+
await route2(endpoint, canonicalTask, objective, model, sessionId, contextTokens, effectiveRequiredCapabilities, minConfidence, executionPreference, minPlacementEvidence)
|
|
23331
|
+
);
|
|
23332
|
+
if (!isStructuredSuccess(decision)) return decision;
|
|
23333
|
+
}
|
|
23334
|
+
const result = parsedResult(
|
|
23335
|
+
await runTaskRequest2(endpoint ?? DEFAULT_SERVE_ENDPOINT, {
|
|
23336
|
+
task: scopeId !== void 0 && task === void 0 ? void 0 : canonicalTask,
|
|
23337
|
+
objective: objective ?? (scopeId === void 0 ? "balanced" : void 0),
|
|
23338
|
+
model,
|
|
23339
|
+
session_id: sessionId,
|
|
23340
|
+
scope_id: scopeId,
|
|
23341
|
+
scope_revision: scopeRevision,
|
|
23342
|
+
context_tokens: contextTokens,
|
|
23343
|
+
required_capabilities: effectiveRequiredCapabilities,
|
|
23344
|
+
prompt,
|
|
23345
|
+
images,
|
|
23346
|
+
messages: taskMessages({ systemPrompt, messages, prompt, images }),
|
|
23347
|
+
input,
|
|
23348
|
+
tools,
|
|
23349
|
+
keep_alive: keepAlive,
|
|
23350
|
+
min_confidence: minConfidence,
|
|
23351
|
+
priority: priority ?? "normal",
|
|
23352
|
+
max_wait_seconds: maxWaitSeconds,
|
|
23353
|
+
timeout_seconds: timeoutSeconds,
|
|
23354
|
+
stop_on_repetition: stopOnRepetition,
|
|
23355
|
+
defer: defer ?? false,
|
|
23356
|
+
execution_preference: executionPreference,
|
|
23357
|
+
min_placement_evidence: minPlacementEvidence,
|
|
23358
|
+
request_options: {
|
|
23359
|
+
format,
|
|
23360
|
+
think,
|
|
23361
|
+
options,
|
|
23362
|
+
logprobs,
|
|
23363
|
+
top_logprobs: topLogprobs
|
|
23364
|
+
}
|
|
23365
|
+
})
|
|
23366
|
+
);
|
|
23367
|
+
if (isStructuredSuccess(result) && result.structuredContent.deferred === true) return result;
|
|
23368
|
+
if (!returnEmbeddings && isStructuredSuccess(result)) {
|
|
23369
|
+
const trimmed = summarizeEmbeddings(result.structuredContent);
|
|
23370
|
+
if (trimmed) return withTaskTelemetry(structuredResult(trimmed));
|
|
23371
|
+
}
|
|
23372
|
+
return withTaskTelemetry(result);
|
|
23373
|
+
} catch (error2) {
|
|
23374
|
+
return errorResult(error2);
|
|
23375
|
+
}
|
|
23376
|
+
}
|
|
23377
|
+
);
|
|
23378
|
+
}
|
|
23379
|
+
|
|
23380
|
+
// src/tools/task-jobs.ts
|
|
23381
|
+
function registerTaskJobsTool(server2) {
|
|
23382
|
+
server2.registerTool(
|
|
23383
|
+
"task_jobs",
|
|
23384
|
+
{
|
|
23385
|
+
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.",
|
|
23386
|
+
inputSchema: external_exports.object({
|
|
23387
|
+
endpoint: endpointParam,
|
|
23388
|
+
action: external_exports.enum(["list", "get", "cancel", "remove"]),
|
|
23389
|
+
jobId: external_exports.string().uuid().optional(),
|
|
23390
|
+
returnEmbeddings: external_exports.boolean().optional()
|
|
23391
|
+
}).strict(),
|
|
23392
|
+
outputSchema: objectResultSchema,
|
|
23393
|
+
annotations: { destructiveHint: true }
|
|
23394
|
+
},
|
|
23395
|
+
async ({ endpoint, action, jobId, returnEmbeddings }) => {
|
|
23396
|
+
try {
|
|
23397
|
+
if (action === "list" ? jobId !== void 0 : jobId === void 0) {
|
|
23398
|
+
return errorResult(new Error("Use jobId only with get/cancel/remove; all require a jobId."));
|
|
23399
|
+
}
|
|
23400
|
+
if (returnEmbeddings !== void 0 && action !== "get") {
|
|
23401
|
+
return errorResult(new Error("returnEmbeddings is valid only with get."));
|
|
23402
|
+
}
|
|
23403
|
+
const result = parsedResult(action === "list" ? await listTaskJobs2(endpoint) : action === "get" ? await getTaskJob2(endpoint, jobId) : action === "cancel" ? await cancelTaskJob2(endpoint, jobId) : await removeTaskJob2(endpoint, jobId));
|
|
23404
|
+
if (!returnEmbeddings && isStructuredSuccess(result)) {
|
|
23405
|
+
const job = result.structuredContent.job;
|
|
23406
|
+
if (job?.result && typeof job.result === "object") {
|
|
23407
|
+
const trimmed = summarizeEmbeddings(job.result);
|
|
23408
|
+
if (trimmed) return structuredResult({ ...result.structuredContent, job: { ...job, result: trimmed } });
|
|
23409
|
+
}
|
|
23410
|
+
}
|
|
23411
|
+
return result;
|
|
23412
|
+
} catch (error2) {
|
|
23413
|
+
return errorResult(error2);
|
|
23414
|
+
}
|
|
23415
|
+
}
|
|
23416
|
+
);
|
|
23417
|
+
}
|
|
23418
|
+
|
|
23419
|
+
// src/tools/ollama-lifecycle.ts
|
|
23420
|
+
var manageResultSchema = external_exports.object({
|
|
23421
|
+
status: external_exports.string().optional(),
|
|
23422
|
+
progress: external_exports.unknown().optional()
|
|
23423
|
+
}).passthrough();
|
|
23424
|
+
var deleteResultSchema = external_exports.object({
|
|
23425
|
+
deleted: external_exports.string()
|
|
23426
|
+
}).passthrough();
|
|
23427
|
+
function summarizeOllamaPullStream(text) {
|
|
23428
|
+
const events = [];
|
|
23429
|
+
for (const line of text.split(/\r?\n/)) {
|
|
23430
|
+
const trimmed = line.trim();
|
|
23431
|
+
if (!trimmed) continue;
|
|
22816
23432
|
try {
|
|
22817
|
-
const parsed = JSON.parse(
|
|
22818
|
-
if (
|
|
22819
|
-
|
|
23433
|
+
const parsed = JSON.parse(trimmed);
|
|
23434
|
+
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
23435
|
+
events.push(parsed);
|
|
22820
23436
|
}
|
|
22821
|
-
offset = parsed.offset;
|
|
22822
23437
|
} catch {
|
|
22823
|
-
throw new Error("`cursor` is invalid or the model list changed; restart without cursor.");
|
|
22824
23438
|
}
|
|
22825
23439
|
}
|
|
22826
|
-
|
|
22827
|
-
|
|
22828
|
-
return {
|
|
22829
|
-
items: page,
|
|
22830
|
-
returned: page.length,
|
|
22831
|
-
total: items.length,
|
|
22832
|
-
next_cursor: nextOffset < items.length ? Buffer.from(JSON.stringify({ offset: nextOffset, fingerprint }), "utf8").toString("base64url") : null
|
|
22833
|
-
};
|
|
22834
|
-
}
|
|
22835
|
-
var liveDelegates = /* @__PURE__ */ new Set();
|
|
22836
|
-
function killLiveDelegates() {
|
|
22837
|
-
for (const child2 of liveDelegates) {
|
|
23440
|
+
if (events.length === 0) {
|
|
23441
|
+
if (!text.trim()) return { status: "empty" };
|
|
22838
23442
|
try {
|
|
22839
|
-
|
|
23443
|
+
const parsed = JSON.parse(text);
|
|
23444
|
+
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
23445
|
+
return parsed;
|
|
23446
|
+
}
|
|
22840
23447
|
} catch {
|
|
23448
|
+
return { status: "unparsed", raw: clipText(text, 500) };
|
|
22841
23449
|
}
|
|
23450
|
+
return { status: "unparsed", raw: clipText(text, 500) };
|
|
22842
23451
|
}
|
|
22843
|
-
|
|
23452
|
+
const last = events[events.length - 1] ?? {};
|
|
23453
|
+
const withBytes = [...events].reverse().find((event) => typeof event.total === "number" && event.total > 0);
|
|
23454
|
+
const completed = typeof withBytes?.completed === "number" ? withBytes.completed : null;
|
|
23455
|
+
const total = typeof withBytes?.total === "number" ? withBytes.total : null;
|
|
23456
|
+
const percent = completed != null && total != null && total > 0 ? Math.round(completed / total * 1e3) / 10 : null;
|
|
23457
|
+
const status2 = typeof last.error === "string" && last.error ? "error" : last.status;
|
|
23458
|
+
return {
|
|
23459
|
+
...last,
|
|
23460
|
+
...status2 === void 0 ? {} : { status: status2 },
|
|
23461
|
+
progress: {
|
|
23462
|
+
events: events.length,
|
|
23463
|
+
completed,
|
|
23464
|
+
total,
|
|
23465
|
+
percent,
|
|
23466
|
+
lastStatus: last.status ?? null
|
|
23467
|
+
}
|
|
23468
|
+
};
|
|
22844
23469
|
}
|
|
22845
|
-
|
|
22846
|
-
|
|
22847
|
-
|
|
22848
|
-
|
|
23470
|
+
async function ollamaPull(endpoint, model, timeoutSeconds, options = {}) {
|
|
23471
|
+
const raw = await ollamaFetch(endpoint, "/api/pull", {
|
|
23472
|
+
method: "POST",
|
|
23473
|
+
body: { model, stream: true },
|
|
23474
|
+
timeoutMs: (timeoutSeconds ?? DEFAULT_PULL_TIMEOUT_SECONDS) * 1e3,
|
|
23475
|
+
parse: "ndjson",
|
|
23476
|
+
signal: options.signal,
|
|
23477
|
+
onLine: options.onProgress
|
|
22849
23478
|
});
|
|
23479
|
+
if (typeof raw === "string") return summarizeOllamaPullStream(raw);
|
|
23480
|
+
if (raw && typeof raw === "object" && !Array.isArray(raw)) return raw;
|
|
23481
|
+
return { status: "empty" };
|
|
22850
23482
|
}
|
|
22851
|
-
process.on("exit", () => {
|
|
22852
|
-
killLiveDelegates();
|
|
22853
|
-
stopAutostartedServe();
|
|
22854
|
-
});
|
|
22855
|
-
process.stdin.on("close", () => {
|
|
22856
|
-
killLiveDelegates();
|
|
22857
|
-
stopAutostartedServe();
|
|
22858
|
-
process.exit(0);
|
|
22859
|
-
});
|
|
22860
23483
|
function pullProgressReporter(extra) {
|
|
22861
23484
|
const token = extra._meta?.progressToken;
|
|
22862
23485
|
if (token === void 0) return void 0;
|
|
@@ -22880,769 +23503,245 @@ function pullProgressReporter(extra) {
|
|
|
22880
23503
|
}).catch(() => void 0);
|
|
22881
23504
|
};
|
|
22882
23505
|
}
|
|
22883
|
-
|
|
22884
|
-
|
|
22885
|
-
|
|
22886
|
-
process.exit(0);
|
|
22887
|
-
});
|
|
22888
|
-
}
|
|
22889
|
-
var INSTRUCTIONS = `models{view:"installed"}; models{view:"resident"} for residency.
|
|
22890
|
-
run_task preview never executes; code_review aliases coding. Preview routing only; requiredCapabilities:["tools"] previews tools; omit preview and supply the payload. Caller executes tools.
|
|
22891
|
-
quality needs policy/model; medium needs policy+benchmark; observed needs /api/ps proof.
|
|
22892
|
-
Scoped run_task needs scopeId+scopeRevision. Scopes store history; sessions affinity, not messages/KV. defer:true: job.id\u2192task_jobs.jobId unchanged. Restart loses scopes/sessions/jobs.
|
|
22893
|
-
Check isError first; prefer structuredContent, else content[].text. page.next_cursor\u2192cursor unchanged.
|
|
22894
|
-
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.
|
|
22895
|
-
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.
|
|
22896
|
-
serve :11435; default-endpoint autostart optional. Docs: freellama://docs/index.`;
|
|
22897
|
-
var server = new McpServer(
|
|
22898
|
-
{ name: "freellama", version: SERVER_VERSION },
|
|
22899
|
-
{ instructions: INSTRUCTIONS }
|
|
22900
|
-
);
|
|
22901
|
-
var PACKAGED_DOCS_DIR = path5.resolve(path5.dirname(fileURLToPath3(import.meta.url)), "..", "docs");
|
|
22902
|
-
if (!existsSync5(PACKAGED_DOCS_DIR)) {
|
|
22903
|
-
throw new Error(`FreeLlama MCP documentation is missing at ${PACKAGED_DOCS_DIR}; run the package build.`);
|
|
22904
|
-
}
|
|
22905
|
-
var PACKAGED_DEV_DOCS_DIR = path5.join(PACKAGED_DOCS_DIR, "dev");
|
|
22906
|
-
var packagedDocs = [
|
|
22907
|
-
...readdirSync(PACKAGED_DOCS_DIR).filter((name) => name.endsWith(".md")).sort(),
|
|
22908
|
-
...existsSync5(PACKAGED_DEV_DOCS_DIR) ? readdirSync(PACKAGED_DEV_DOCS_DIR).filter((name) => name.endsWith(".md")).sort().map((name) => `dev/${name}`) : []
|
|
22909
|
-
];
|
|
22910
|
-
var packagedDocNames = packagedDocs.map((name) => path5.basename(name));
|
|
22911
|
-
if (new Set(packagedDocNames).size !== packagedDocNames.length) {
|
|
22912
|
-
throw new Error(`FreeLlama MCP documentation has duplicate names across docs/ and docs/dev/: ${packagedDocs.join(", ")}`);
|
|
22913
|
-
}
|
|
22914
|
-
if (!packagedDocs.includes("INDEX.md")) {
|
|
22915
|
-
throw new Error(`FreeLlama MCP documentation index is missing at ${PACKAGED_DOCS_DIR}/INDEX.md; run the package build.`);
|
|
22916
|
-
}
|
|
22917
|
-
for (const name of packagedDocs) {
|
|
22918
|
-
const base = path5.basename(name);
|
|
22919
|
-
const uri = `freellama://docs/${base === "INDEX.md" ? "index" : base.replace(/\.md$/, "")}`;
|
|
22920
|
-
server.registerResource(
|
|
22921
|
-
`freellama-docs-${base.toLowerCase().replace(/\.md$/, "")}`,
|
|
22922
|
-
uri,
|
|
23506
|
+
function registerOllamaLifecycleTools(server2) {
|
|
23507
|
+
server2.registerTool(
|
|
23508
|
+
"ollama_manage",
|
|
22923
23509
|
{
|
|
22924
|
-
|
|
22925
|
-
|
|
23510
|
+
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.",
|
|
23511
|
+
inputSchema: external_exports.object({
|
|
23512
|
+
action: external_exports.enum(["pull", "stop"]),
|
|
23513
|
+
model: external_exports.string().min(1),
|
|
23514
|
+
ollamaEndpoint: ollamaEndpointParam,
|
|
23515
|
+
timeoutSeconds: external_exports.number().int().positive().optional()
|
|
23516
|
+
}).strict(),
|
|
23517
|
+
outputSchema: manageResultSchema,
|
|
23518
|
+
annotations: { destructiveHint: false }
|
|
22926
23519
|
},
|
|
22927
|
-
async () =>
|
|
22928
|
-
contents: [{ uri, mimeType: "text/markdown", text: await readFile(path5.join(PACKAGED_DOCS_DIR, name), "utf8") }]
|
|
22929
|
-
})
|
|
22930
|
-
);
|
|
22931
|
-
}
|
|
22932
|
-
server.registerTool(
|
|
22933
|
-
"doctor",
|
|
22934
|
-
{
|
|
22935
|
-
description: "Use when: diagnosis/status/usage. Do not use when: choosing models. Returns: summary; opt into detail.",
|
|
22936
|
-
inputSchema: external_exports.object({
|
|
22937
|
-
endpoint: ollamaEndpointParam,
|
|
22938
|
-
serveEndpoint: endpointParam,
|
|
22939
|
-
view: external_exports.enum(["summary", "scheduler", "config", "full", "status", "usage"]).optional(),
|
|
22940
|
-
days: external_exports.number().int().min(1).max(366).optional()
|
|
22941
|
-
}).strict(),
|
|
22942
|
-
outputSchema: doctorResultSchema,
|
|
22943
|
-
annotations: { readOnlyHint: true }
|
|
22944
|
-
},
|
|
22945
|
-
async ({ endpoint, serveEndpoint, view, days }) => {
|
|
22946
|
-
try {
|
|
22947
|
-
if (days !== void 0 && view !== "usage") return errorResult(new Error("days is only valid with view: usage."));
|
|
22948
|
-
if (view === "status") return parsedResult(await status(serveEndpoint));
|
|
22949
|
-
if (view === "usage") return parsedResult(await usage(serveEndpoint, days));
|
|
22950
|
-
const report = parsedResult(await doctor(endpoint));
|
|
22951
|
-
if (!isStructuredSuccess(report)) return report;
|
|
23520
|
+
async ({ action, model, ollamaEndpoint, timeoutSeconds }, extra) => {
|
|
22952
23521
|
try {
|
|
22953
|
-
|
|
22954
|
-
|
|
22955
|
-
|
|
22956
|
-
|
|
22957
|
-
|
|
23522
|
+
if (action === "stop" && timeoutSeconds !== void 0) {
|
|
23523
|
+
return errorResult(new Error('`timeoutSeconds` is valid only for action "pull".'));
|
|
23524
|
+
}
|
|
23525
|
+
const data = action === "pull" ? await ollamaPull(ollamaEndpoint, model, timeoutSeconds, {
|
|
23526
|
+
signal: extra.signal,
|
|
23527
|
+
onProgress: pullProgressReporter(extra)
|
|
23528
|
+
}) : await ollamaFetch(ollamaEndpoint, "/api/generate", {
|
|
23529
|
+
method: "POST",
|
|
23530
|
+
body: { model, keep_alive: 0 },
|
|
23531
|
+
signal: extra.signal
|
|
23532
|
+
});
|
|
23533
|
+
const payload = data;
|
|
23534
|
+
if (typeof payload.error === "string" && payload.error) {
|
|
23535
|
+
return errorResult(new Error(`${action} ${model} failed: ${payload.error}`));
|
|
22958
23536
|
}
|
|
22959
|
-
|
|
22960
|
-
try {
|
|
22961
|
-
report.structuredContent.platform_health = JSON.parse(await health(serveEndpoint));
|
|
23537
|
+
return structuredResult(payload);
|
|
22962
23538
|
} catch (error2) {
|
|
22963
|
-
|
|
23539
|
+
return errorResult(error2);
|
|
22964
23540
|
}
|
|
22965
|
-
|
|
22966
|
-
|
|
22967
|
-
|
|
22968
|
-
|
|
22969
|
-
|
|
22970
|
-
|
|
22971
|
-
|
|
22972
|
-
|
|
22973
|
-
|
|
22974
|
-
|
|
22975
|
-
|
|
22976
|
-
|
|
23541
|
+
}
|
|
23542
|
+
);
|
|
23543
|
+
server2.registerTool(
|
|
23544
|
+
"ollama_delete",
|
|
23545
|
+
{
|
|
23546
|
+
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.",
|
|
23547
|
+
inputSchema: external_exports.object({
|
|
23548
|
+
model: external_exports.string().min(1),
|
|
23549
|
+
ollamaEndpoint: ollamaEndpointParam
|
|
23550
|
+
}).strict(),
|
|
23551
|
+
outputSchema: deleteResultSchema,
|
|
23552
|
+
annotations: { destructiveHint: true }
|
|
23553
|
+
},
|
|
23554
|
+
async ({ model, ollamaEndpoint }) => {
|
|
23555
|
+
try {
|
|
23556
|
+
await ollamaFetch(ollamaEndpoint, "/api/delete", {
|
|
23557
|
+
method: "DELETE",
|
|
23558
|
+
body: { model }
|
|
22977
23559
|
});
|
|
23560
|
+
return structuredResult({ deleted: model });
|
|
23561
|
+
} catch (error2) {
|
|
23562
|
+
return errorResult(error2);
|
|
22978
23563
|
}
|
|
22979
|
-
if ((view ?? "summary") === "full") return structuredResult(full);
|
|
22980
|
-
const running = full.running?.models ?? [];
|
|
22981
|
-
const platformHealth = full.platform_health;
|
|
22982
|
-
const compact = {
|
|
22983
|
-
status: "ok",
|
|
22984
|
-
summary: `Ollama ${String(full.version?.version ?? "unknown")}; ${running.length} resident model(s).`,
|
|
22985
|
-
endpoint: full.endpoint,
|
|
22986
|
-
ollama: { endpoint: full.endpoint, version: full.version, resident_model_count: running.length },
|
|
22987
|
-
machine: full.machine,
|
|
22988
|
-
local_conservative_config_posture: full.local_conservative_config_posture,
|
|
22989
|
-
host_runtime_signals: full.host_runtime_signals,
|
|
22990
|
-
...view === "scheduler" ? {
|
|
22991
|
-
scheduler: {
|
|
22992
|
-
admission: platformHealth?.admission ?? null,
|
|
22993
|
-
ollama_num_parallel: flatConfig?.OLLAMA_NUM_PARALLEL,
|
|
22994
|
-
ollama_max_queue: flatConfig?.OLLAMA_MAX_QUEUE,
|
|
22995
|
-
proof_level: "configured_and_snapshot_only; measure concurrent execution before claiming throughput"
|
|
22996
|
-
}
|
|
22997
|
-
} : {},
|
|
22998
|
-
evidence: { runtime: "ollama_api_version_and_ps", configuration: full.ollama_env_config_source },
|
|
22999
|
-
next: view === "scheduler" ? "Use run_task preview for a per-task advisory receipt." : 'Use models{view:"installed"} to select a local model.'
|
|
23000
|
-
};
|
|
23001
|
-
return structuredResult(compact);
|
|
23002
|
-
} catch (error2) {
|
|
23003
|
-
return errorResult(error2);
|
|
23004
23564
|
}
|
|
23005
|
-
|
|
23006
|
-
|
|
23007
|
-
|
|
23008
|
-
|
|
23009
|
-
|
|
23010
|
-
|
|
23011
|
-
|
|
23012
|
-
|
|
23013
|
-
|
|
23014
|
-
|
|
23015
|
-
|
|
23016
|
-
|
|
23017
|
-
|
|
23018
|
-
|
|
23019
|
-
|
|
23020
|
-
|
|
23021
|
-
|
|
23022
|
-
|
|
23023
|
-
|
|
23024
|
-
}
|
|
23025
|
-
|
|
23026
|
-
|
|
23027
|
-
|
|
23028
|
-
|
|
23029
|
-
|
|
23030
|
-
|
|
23031
|
-
}
|
|
23032
|
-
}
|
|
23033
|
-
);
|
|
23034
|
-
server.registerTool(
|
|
23035
|
-
"models",
|
|
23036
|
-
{
|
|
23037
|
-
description: "Use when: inventory/library. Do not use when: execution/mutation. Returns: models; library finds families, then one family's tags.",
|
|
23038
|
-
inputSchema: external_exports.object({
|
|
23039
|
-
view: external_exports.enum(["installed", "resident", "detail", "raw", "library"]).optional(),
|
|
23040
|
-
model: external_exports.string().min(1).optional(),
|
|
23041
|
-
includeVerbose: external_exports.boolean().optional(),
|
|
23042
|
-
includeLibrary: external_exports.boolean().optional(),
|
|
23043
|
-
includeReadme: external_exports.boolean().optional(),
|
|
23044
|
-
query: external_exports.string().min(1).optional(),
|
|
23045
|
-
capabilities: external_exports.array(external_exports.enum(["vision", "tools", "thinking", "embedding", "cloud"])).min(1).max(5).optional(),
|
|
23046
|
-
order: external_exports.enum(["popular", "newest"]).optional(),
|
|
23047
|
-
limit: external_exports.number().int().positive().max(50).optional(),
|
|
23048
|
-
cursor: external_exports.string().min(1).optional(),
|
|
23049
|
-
endpoint: endpointParam,
|
|
23050
|
-
ollamaEndpoint: ollamaEndpointParam
|
|
23051
|
-
}).strict(),
|
|
23052
|
-
outputSchema: modelsResultSchema,
|
|
23053
|
-
annotations: { readOnlyHint: true }
|
|
23054
|
-
},
|
|
23055
|
-
async ({ view, model, includeVerbose, includeLibrary, includeReadme, query, capabilities, order, limit, cursor, endpoint, ollamaEndpoint }) => {
|
|
23056
|
-
try {
|
|
23057
|
-
const selectedView = view ?? "installed";
|
|
23058
|
-
if (includeLibrary !== void 0 && selectedView !== "installed" && selectedView !== "detail") {
|
|
23059
|
-
return errorResult(new Error('`includeLibrary` is valid only for views "installed" and "detail".'));
|
|
23060
|
-
}
|
|
23061
|
-
if (includeReadme !== void 0 && !(selectedView === "library" && model || selectedView === "detail" && includeLibrary === true)) {
|
|
23062
|
-
return errorResult(new Error('`includeReadme` needs library step 2, or view "detail" with `includeLibrary:true`.'));
|
|
23063
|
-
}
|
|
23064
|
-
if (selectedView !== "library" && [query, capabilities, order].some((value) => value !== void 0)) {
|
|
23065
|
-
return errorResult(new Error(`view "${selectedView}" does not accept library search fields.`));
|
|
23066
|
-
}
|
|
23067
|
-
if (selectedView !== "library" && selectedView !== "raw" && [limit, cursor].some((value) => value !== void 0)) {
|
|
23068
|
-
return errorResult(new Error(`view "${selectedView}" accepts neither pagination nor library search fields.`));
|
|
23069
|
-
}
|
|
23070
|
-
if (selectedView !== "detail" && includeVerbose !== void 0) {
|
|
23071
|
-
return errorResult(new Error('`includeVerbose` is valid only for view "detail".'));
|
|
23072
|
-
}
|
|
23073
|
-
if (selectedView !== "detail" && selectedView !== "library" && model !== void 0) {
|
|
23074
|
-
return errorResult(new Error('`model` is valid only for views "detail" and "library".'));
|
|
23075
|
-
}
|
|
23076
|
-
if (selectedView === "library" && model && [query, capabilities, order].some((value) => value !== void 0)) {
|
|
23077
|
-
return errorResult(
|
|
23078
|
-
new Error("Library step 2 accepts only `model` plus endpoint overrides; omit step-1 search fields.")
|
|
23079
|
-
);
|
|
23080
|
-
}
|
|
23081
|
-
switch (selectedView) {
|
|
23082
|
-
case "raw": {
|
|
23083
|
-
const raw = await ollamaFetch(ollamaEndpoint, "/api/tags");
|
|
23084
|
-
const models = Array.isArray(raw.models) ? raw.models : [];
|
|
23085
|
-
const page = pageLiveList(models, limit, cursor, (model2) => String(model2.name ?? JSON.stringify(model2)));
|
|
23086
|
-
return structuredResult({ ...raw, models: page.items, page: { returned: page.returned, total: page.total, next_cursor: page.next_cursor } });
|
|
23087
|
-
}
|
|
23088
|
-
case "resident": {
|
|
23089
|
-
const managed = parsedResult(await listModels2(endpoint));
|
|
23090
|
-
if (!isStructuredSuccess(managed)) return managed;
|
|
23091
|
-
const data = managed.structuredContent;
|
|
23092
|
-
const models = (data.models ?? []).filter((entry) => entry.resident === true).map((entry) => {
|
|
23093
|
-
const size = typeof entry.resident_size === "number" ? entry.resident_size : null;
|
|
23094
|
-
const vram = typeof entry.resident_vram === "number" ? entry.resident_vram : null;
|
|
23095
|
-
if (size === null || vram === null || size === 0) return entry;
|
|
23096
|
-
const gpuPercent = Math.round(vram / size * 100);
|
|
23097
|
-
const execution = entry.execution;
|
|
23098
|
-
const assignedCpu = execution?.placement === "cpu";
|
|
23099
|
-
const observedProcessor = execution?.observation?.processor;
|
|
23100
|
-
const processor = observedProcessor === "cpu" || observedProcessor === "gpu" || observedProcessor === "mixed" ? observedProcessor : gpuPercent >= 100 ? "gpu" : gpuPercent <= 0 ? "cpu" : "mixed";
|
|
23101
|
-
const mismatch = execution?.observation?.status === "mismatch";
|
|
23102
|
-
return {
|
|
23103
|
-
...entry,
|
|
23104
|
-
placement: {
|
|
23105
|
-
gpu_percent: gpuPercent,
|
|
23106
|
-
assigned: assignedCpu,
|
|
23107
|
-
processor: processor === "gpu" ? "100% GPU" : processor === "cpu" ? "100% CPU" : `${gpuPercent}% GPU / ${100 - gpuPercent}% CPU`,
|
|
23108
|
-
...mismatch ? {
|
|
23109
|
-
warning: `Configured ${execution?.placement ?? "unknown"} backend disagrees with Ollama /api/ps: ${processor} was physically observed. This sample is excluded from adaptive routing feedback.`
|
|
23110
|
-
} : processor === "mixed" ? {
|
|
23111
|
-
warning: 'Partially offloaded to CPU \u2014 expect a large slowdown. Free VRAM (`ollama_manage` action "stop") or lower the context length.'
|
|
23112
|
-
} : {}
|
|
23113
|
-
}
|
|
23114
|
-
};
|
|
23115
|
-
});
|
|
23116
|
-
return structuredResult({ ...data, models });
|
|
23117
|
-
}
|
|
23118
|
-
case "detail": {
|
|
23119
|
-
if (!model) {
|
|
23120
|
-
return errorResult(new Error('view "detail" needs `model` set to an installed tag.'));
|
|
23121
|
-
}
|
|
23122
|
-
const data = await ollamaFetch(ollamaEndpoint, "/api/show", {
|
|
23123
|
-
method: "POST",
|
|
23124
|
-
body: { model }
|
|
23125
|
-
});
|
|
23126
|
-
let local = data;
|
|
23127
|
-
if (includeLibrary && !data.remote_host && !data.remote_model && !/-cloud$/i.test(model)) {
|
|
23128
|
-
try {
|
|
23129
|
-
const inventory = await ollamaFetch(ollamaEndpoint, "/api/tags");
|
|
23130
|
-
const canonical = libraryReference(model)?.tag;
|
|
23131
|
-
const entry = canonical && inventory.models?.find((entry2) => libraryReference(String(entry2.name ?? entry2.model))?.tag === canonical);
|
|
23132
|
-
if (entry) local = { ...entry, ...data, digest: entry.digest };
|
|
23133
|
-
} catch {
|
|
23134
|
-
}
|
|
23135
|
-
}
|
|
23136
|
-
const library = includeLibrary && !local.remote_host && !local.remote_model && !/-cloud$/i.test(model) ? await ollamaLibrary.lookup(model) : void 0;
|
|
23137
|
-
const { license, modelfile, ...rest } = data;
|
|
23138
|
-
const modelInfo = rest.model_info ?? {};
|
|
23139
|
-
const contextEntry = Object.entries(modelInfo).find(
|
|
23140
|
-
([key, value]) => key.endsWith(".context_length") && typeof value === "number"
|
|
23141
|
-
);
|
|
23142
|
-
return structuredResult({
|
|
23143
|
-
...rest,
|
|
23144
|
-
max_context_length: contextEntry?.[1] ?? null,
|
|
23145
|
-
knowledge: modelKnowledge(model, local, library, includeReadme),
|
|
23146
|
-
...includeVerbose ? { license, modelfile } : {}
|
|
23147
|
-
});
|
|
23148
|
-
}
|
|
23149
|
-
case "library":
|
|
23150
|
-
return await libraryLookup({ model, query, capabilities, order, limit, cursor, includeReadme, endpoint, ollamaEndpoint });
|
|
23151
|
-
default: {
|
|
23152
|
-
const result = parsedResult(await listModels2(endpoint));
|
|
23153
|
-
if (!isStructuredSuccess(result)) return result;
|
|
23154
|
-
const data = result.structuredContent;
|
|
23155
|
-
return structuredResult({ ...data, models: await enrichInstalledModels(
|
|
23156
|
-
data.models ?? [],
|
|
23157
|
-
ollamaLibrary,
|
|
23158
|
-
includeLibrary ?? false,
|
|
23159
|
-
async (upstream) => (await ollamaFetch(upstream ?? ollamaEndpoint, "/api/tags")).models ?? []
|
|
23160
|
-
) });
|
|
23161
|
-
}
|
|
23565
|
+
);
|
|
23566
|
+
}
|
|
23567
|
+
|
|
23568
|
+
// src/tools/delegate-research.ts
|
|
23569
|
+
import { existsSync as existsSync5 } from "node:fs";
|
|
23570
|
+
import { mkdtemp, readFile as readFile2, rm, writeFile } from "node:fs/promises";
|
|
23571
|
+
import { tmpdir } from "node:os";
|
|
23572
|
+
import path5 from "node:path";
|
|
23573
|
+
|
|
23574
|
+
// src/delegate.ts
|
|
23575
|
+
import { existsSync as existsSync4, readFileSync, realpathSync, statSync } from "node:fs";
|
|
23576
|
+
import path4 from "node:path";
|
|
23577
|
+
function researchErrorResult(error2, diagnostic) {
|
|
23578
|
+
const result = errorResult(error2);
|
|
23579
|
+
const summary = diagnostic ?? result.content[0].text;
|
|
23580
|
+
return {
|
|
23581
|
+
...result,
|
|
23582
|
+
content: [{ type: "text", text: summary }],
|
|
23583
|
+
structuredContent: {
|
|
23584
|
+
...result.structuredContent ?? { error: summary },
|
|
23585
|
+
answer: "",
|
|
23586
|
+
summary,
|
|
23587
|
+
verification: {
|
|
23588
|
+
recommendation: "escalate",
|
|
23589
|
+
grounded: false,
|
|
23590
|
+
why: "The research request failed; no usable answer was produced."
|
|
23162
23591
|
}
|
|
23163
|
-
} catch (error2) {
|
|
23164
|
-
return errorResult(error2);
|
|
23165
|
-
}
|
|
23166
|
-
}
|
|
23167
|
-
);
|
|
23168
|
-
async function libraryLookup({
|
|
23169
|
-
model,
|
|
23170
|
-
query,
|
|
23171
|
-
capabilities,
|
|
23172
|
-
order,
|
|
23173
|
-
limit,
|
|
23174
|
-
cursor,
|
|
23175
|
-
includeReadme,
|
|
23176
|
-
endpoint,
|
|
23177
|
-
ollamaEndpoint
|
|
23178
|
-
}) {
|
|
23179
|
-
const localState = async () => {
|
|
23180
|
-
let installed = /* @__PURE__ */ new Set();
|
|
23181
|
-
let memoryBytes = null;
|
|
23182
|
-
try {
|
|
23183
|
-
const tags = await ollamaFetch(ollamaEndpoint, "/api/tags");
|
|
23184
|
-
installed = new Set((tags.models ?? []).map((m) => m.name ?? ""));
|
|
23185
|
-
} catch {
|
|
23186
|
-
}
|
|
23187
|
-
try {
|
|
23188
|
-
const profile = JSON.parse(await machine(endpoint));
|
|
23189
|
-
memoryBytes = profile.memory_bytes ?? profile.unified_memory_bytes ?? null;
|
|
23190
|
-
} catch {
|
|
23191
23592
|
}
|
|
23192
|
-
return { installed, memoryBytes };
|
|
23193
23593
|
};
|
|
23194
|
-
try {
|
|
23195
|
-
if (model) {
|
|
23196
|
-
const ref = libraryReference(model);
|
|
23197
|
-
if (!ref) throw new Error("Use an exact Ollama family or namespace/model name, not a URL or path.");
|
|
23198
|
-
const { family } = ref;
|
|
23199
|
-
const library = await ollamaLibrary.lookup(model);
|
|
23200
|
-
if (library.status !== "available") return structuredResult({
|
|
23201
|
-
family,
|
|
23202
|
-
sourcePage: ref.url,
|
|
23203
|
-
tags: [],
|
|
23204
|
-
tagsUnavailable: library.message,
|
|
23205
|
-
library,
|
|
23206
|
-
recommendation: null
|
|
23207
|
-
});
|
|
23208
|
-
const tags = library.tags;
|
|
23209
|
-
const { installed: installed2, memoryBytes } = await localState();
|
|
23210
|
-
const budget = memoryBytes ? memoryBytes * 0.6 : null;
|
|
23211
|
-
const annotated = tags.map((t) => ({
|
|
23212
|
-
...t,
|
|
23213
|
-
installed: installed2.has(t.tag),
|
|
23214
|
-
fitsInMemory: budget && t.sizeBytes ? t.sizeBytes <= budget : null,
|
|
23215
|
-
fitScope: "host_memory_budget_only"
|
|
23216
|
-
}));
|
|
23217
|
-
const runnable = annotated.filter((t) => t.fitsInMemory === true && !t.cloud);
|
|
23218
|
-
const isEmbeddingFamily = library.card.capabilities.includes("embedding");
|
|
23219
|
-
const sized = runnable.filter((t) => t.sizeBytes);
|
|
23220
|
-
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;
|
|
23221
|
-
const tagPage = pageLiveList(annotated, limit, cursor, (tag) => tag.tag);
|
|
23222
|
-
return structuredResult({
|
|
23223
|
-
family,
|
|
23224
|
-
sourcePage: ref.url,
|
|
23225
|
-
library: {
|
|
23226
|
-
status: library.status,
|
|
23227
|
-
sourceUrl: library.sourceUrl,
|
|
23228
|
-
tagsSourceUrl: library.tagsSourceUrl,
|
|
23229
|
-
fetchedAt: library.fetchedAt,
|
|
23230
|
-
expiresAt: library.expiresAt,
|
|
23231
|
-
cached: library.cached,
|
|
23232
|
-
...publicModelGuidance(library, void 0, includeReadme)
|
|
23233
|
-
},
|
|
23234
|
-
...tags.length === 0 ? {
|
|
23235
|
-
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.`
|
|
23236
|
-
} : {},
|
|
23237
|
-
machineMemoryBytes: memoryBytes,
|
|
23238
|
-
fitBudgetBytes: budget,
|
|
23239
|
-
tags: tagPage.items,
|
|
23240
|
-
page: { returned: tagPage.returned, total: tagPage.total, next_cursor: tagPage.next_cursor },
|
|
23241
|
-
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.",
|
|
23242
|
-
recommendation: best ? libraryTrialCandidate(best.tag, isEmbeddingFamily) : null
|
|
23243
|
-
});
|
|
23244
|
-
}
|
|
23245
|
-
const params = new URLSearchParams();
|
|
23246
|
-
for (const c of capabilities ?? []) params.append("c", c);
|
|
23247
|
-
if (query) params.set("q", query);
|
|
23248
|
-
if (order === "newest") params.set("o", "newest");
|
|
23249
|
-
const search = await ollamaLibrary.search(params);
|
|
23250
|
-
const url = search.url;
|
|
23251
|
-
const parsed = search.models.slice(0, limit ?? 10);
|
|
23252
|
-
const { installed } = await localState();
|
|
23253
|
-
const installedFamilies = new Set([...installed].map((n) => n.split(":")[0]));
|
|
23254
|
-
return structuredResult({
|
|
23255
|
-
query: url,
|
|
23256
|
-
fetchedAt: search.fetchedAt,
|
|
23257
|
-
...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." } : {},
|
|
23258
|
-
order: order ?? "popular",
|
|
23259
|
-
count: parsed.length,
|
|
23260
|
-
nextStep: 'Not pullable yet. Call again with view:"library", model:"<name>" to get tags, sizes and memory fit.',
|
|
23261
|
-
models: parsed.map((m) => ({ ...m, installed: installedFamilies.has(m.name) }))
|
|
23262
|
-
});
|
|
23263
|
-
} catch (error2) {
|
|
23264
|
-
return errorResult(error2);
|
|
23265
|
-
}
|
|
23266
23594
|
}
|
|
23267
|
-
|
|
23268
|
-
|
|
23269
|
-
|
|
23270
|
-
|
|
23271
|
-
|
|
23272
|
-
|
|
23273
|
-
|
|
23274
|
-
task: taskParam.removeDefault().optional().describe("caller owns prompts and output format."),
|
|
23275
|
-
objective: objectiveParam,
|
|
23276
|
-
model: external_exports.string().min(1).optional(),
|
|
23277
|
-
sessionId: external_exports.string().uuid().optional(),
|
|
23278
|
-
scopeId: external_exports.string().uuid().optional(),
|
|
23279
|
-
scopeRevision: external_exports.number().int().nonnegative().optional(),
|
|
23280
|
-
contextTokens: external_exports.number().int().positive().optional().describe("Total input + output window (num_ctx)."),
|
|
23281
|
-
executionPreference: executionPreferenceParam,
|
|
23282
|
-
minPlacementEvidence: minPlacementEvidenceParam,
|
|
23283
|
-
requiredCapabilities: requiredCapabilitiesParam,
|
|
23284
|
-
prompt: external_exports.string().min(1).optional(),
|
|
23285
|
-
systemPrompt: systemPromptParam,
|
|
23286
|
-
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"),
|
|
23287
|
-
messages: messagesParam.describe(
|
|
23288
|
-
"Caller system prompts; no injected task instructions. Overrides prompt; extra fields preserved."
|
|
23289
|
-
),
|
|
23290
|
-
input: external_exports.union([external_exports.string().min(1), external_exports.array(external_exports.string().min(1)).min(1)]).optional(),
|
|
23291
|
-
tools: localToolsParam,
|
|
23292
|
-
keepAlive: external_exports.string().min(1).optional().describe('"0" unloads now, "-1" pins; omitted = adaptive finite TTL'),
|
|
23293
|
-
format: external_exports.union([external_exports.literal("json"), external_exports.record(external_exports.unknown())]).optional(),
|
|
23294
|
-
think: external_exports.union([external_exports.boolean(), external_exports.enum(["low", "medium", "high"])]).optional(),
|
|
23295
|
-
options: external_exports.record(external_exports.unknown()).optional().describe("num_ctx/contextTokens and num_gpu/placement are routing-owned; num_predict caps output."),
|
|
23296
|
-
logprobs: external_exports.boolean().optional(),
|
|
23297
|
-
topLogprobs: external_exports.number().int().nonnegative().optional(),
|
|
23298
|
-
minConfidence: minConfidenceParam,
|
|
23299
|
-
priority: external_exports.enum(["interactive", "normal", "background"]).optional(),
|
|
23300
|
-
maxWaitSeconds: external_exports.number().int().positive().optional(),
|
|
23301
|
-
timeoutSeconds: external_exports.number().int().positive().optional(),
|
|
23302
|
-
defer: external_exports.boolean().optional(),
|
|
23303
|
-
returnEmbeddings: external_exports.boolean().optional(),
|
|
23304
|
-
preview: external_exports.boolean().optional().describe(
|
|
23305
|
-
"true = routing fields only; rejects payloads and runtime controls; never executes"
|
|
23306
|
-
)
|
|
23307
|
-
}).strict(),
|
|
23308
|
-
outputSchema: taskResultSchema,
|
|
23309
|
-
annotations: { destructiveHint: false }
|
|
23310
|
-
},
|
|
23311
|
-
async ({
|
|
23312
|
-
endpoint,
|
|
23313
|
-
task,
|
|
23314
|
-
objective,
|
|
23315
|
-
model,
|
|
23316
|
-
sessionId,
|
|
23317
|
-
scopeId,
|
|
23318
|
-
scopeRevision,
|
|
23319
|
-
contextTokens,
|
|
23320
|
-
executionPreference,
|
|
23321
|
-
minPlacementEvidence,
|
|
23322
|
-
requiredCapabilities,
|
|
23323
|
-
prompt,
|
|
23324
|
-
systemPrompt,
|
|
23325
|
-
images,
|
|
23326
|
-
messages,
|
|
23327
|
-
input,
|
|
23328
|
-
tools,
|
|
23329
|
-
keepAlive,
|
|
23330
|
-
format,
|
|
23331
|
-
think,
|
|
23332
|
-
options,
|
|
23333
|
-
logprobs,
|
|
23334
|
-
topLogprobs,
|
|
23335
|
-
minConfidence,
|
|
23336
|
-
priority,
|
|
23337
|
-
maxWaitSeconds,
|
|
23338
|
-
timeoutSeconds,
|
|
23339
|
-
defer,
|
|
23340
|
-
returnEmbeddings,
|
|
23341
|
-
preview
|
|
23342
|
-
}) => {
|
|
23595
|
+
var MODEL_EVIDENCE = (() => {
|
|
23596
|
+
const configured = process.env.FREELLAMA_MCP_MODEL_EVIDENCE;
|
|
23597
|
+
const candidates = [
|
|
23598
|
+
configured,
|
|
23599
|
+
path4.join(REPO_ROOT, "packages/mcp/evidence/model-evidence.json")
|
|
23600
|
+
].filter((x) => Boolean(x));
|
|
23601
|
+
for (const file of candidates) {
|
|
23343
23602
|
try {
|
|
23344
|
-
|
|
23345
|
-
|
|
23346
|
-
|
|
23347
|
-
}
|
|
23348
|
-
if (preview) {
|
|
23349
|
-
const executionOnlyFields = [
|
|
23350
|
-
["scopeId", scopeId],
|
|
23351
|
-
["scopeRevision", scopeRevision],
|
|
23352
|
-
["prompt", prompt],
|
|
23353
|
-
["systemPrompt", systemPrompt],
|
|
23354
|
-
["images", images],
|
|
23355
|
-
["messages", messages],
|
|
23356
|
-
["input", input],
|
|
23357
|
-
["tools", tools],
|
|
23358
|
-
["keepAlive", keepAlive],
|
|
23359
|
-
["format", format],
|
|
23360
|
-
["think", think],
|
|
23361
|
-
["options", options],
|
|
23362
|
-
["logprobs", logprobs],
|
|
23363
|
-
["topLogprobs", topLogprobs],
|
|
23364
|
-
["returnEmbeddings", returnEmbeddings],
|
|
23365
|
-
["maxWaitSeconds", maxWaitSeconds],
|
|
23366
|
-
["priority", priority],
|
|
23367
|
-
["timeoutSeconds", timeoutSeconds],
|
|
23368
|
-
["defer", defer]
|
|
23369
|
-
].filter(([, value]) => value !== void 0).map(([name]) => `\`${name}\``);
|
|
23370
|
-
if (executionOnlyFields.length > 0) {
|
|
23371
|
-
return errorResult(
|
|
23372
|
-
new Error(
|
|
23373
|
-
`\`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.`
|
|
23374
|
-
)
|
|
23375
|
-
);
|
|
23376
|
-
}
|
|
23377
|
-
}
|
|
23378
|
-
const effectiveRequiredCapabilities = tools === void 0 ? requiredCapabilities : withRequiredCapability(requiredCapabilities, "tools");
|
|
23379
|
-
if (topLogprobs !== void 0 && logprobs !== true) {
|
|
23380
|
-
return errorResult(new Error("`topLogprobs` requires `logprobs:true`."));
|
|
23381
|
-
}
|
|
23382
|
-
if (!preview && task === "embedding") {
|
|
23383
|
-
if (input === void 0) return errorResult(new Error('task "embedding" requires `input`.'));
|
|
23384
|
-
if ([prompt, systemPrompt, images, messages, tools, format, think, logprobs, topLogprobs].some(
|
|
23385
|
-
(value) => value !== void 0
|
|
23386
|
-
)) {
|
|
23387
|
-
return errorResult(
|
|
23388
|
-
new Error('task "embedding" accepts `input`, `options`, and routing fields; chat controls are not valid.')
|
|
23389
|
-
);
|
|
23390
|
-
}
|
|
23391
|
-
}
|
|
23392
|
-
if (!preview && task !== "embedding") {
|
|
23393
|
-
if (input !== void 0 || returnEmbeddings !== void 0) {
|
|
23394
|
-
return errorResult(new Error('`input` and `returnEmbeddings` are valid only for task "embedding".'));
|
|
23395
|
-
}
|
|
23396
|
-
if (prompt === void 0 && systemPrompt === void 0 && (messages === void 0 || messages.length === 0)) {
|
|
23397
|
-
return errorResult(new Error(`task "${task}" requires \`prompt\`, \`systemPrompt\`, or \`messages\`.`));
|
|
23398
|
-
}
|
|
23399
|
-
if (images !== void 0 && (messages !== void 0 || prompt === void 0)) {
|
|
23400
|
-
return errorResult(
|
|
23401
|
-
new Error("Top-level `images` is valid only with `prompt`; put images inside messages instead.")
|
|
23402
|
-
);
|
|
23403
|
-
}
|
|
23404
|
-
if (images !== void 0 && model === void 0) {
|
|
23405
|
-
return errorResult(new Error("Images require an explicit tested vision-capable `model`."));
|
|
23406
|
-
}
|
|
23407
|
-
}
|
|
23408
|
-
if (preview) {
|
|
23409
|
-
const result2 = parsedResult(
|
|
23410
|
-
await route2(endpoint, canonicalTask, objective, model, sessionId, contextTokens, effectiveRequiredCapabilities, minConfidence, executionPreference, minPlacementEvidence)
|
|
23411
|
-
);
|
|
23412
|
-
if (isStructuredSuccess(result2)) {
|
|
23413
|
-
const refusal = belowConfidence(result2.structuredContent, minConfidence);
|
|
23414
|
-
if (refusal) return refusal;
|
|
23415
|
-
}
|
|
23416
|
-
return result2;
|
|
23417
|
-
}
|
|
23418
|
-
if (minConfidence && scopeId === void 0) {
|
|
23419
|
-
const decision = parsedResult(
|
|
23420
|
-
// minConfidence is forwarded so the CORE gate refuses, with its actionable message naming
|
|
23421
|
-
// the two commands that raise the grade. The belowConfidence() check below stays only as a
|
|
23422
|
-
// fallback for servers older than the core gate.
|
|
23423
|
-
await route2(endpoint, canonicalTask, objective, model, sessionId, contextTokens, effectiveRequiredCapabilities, minConfidence, executionPreference, minPlacementEvidence)
|
|
23424
|
-
);
|
|
23425
|
-
if (!isStructuredSuccess(decision)) return decision;
|
|
23426
|
-
const refusal = belowConfidence(decision.structuredContent, minConfidence);
|
|
23427
|
-
if (refusal) return refusal;
|
|
23428
|
-
}
|
|
23429
|
-
const result = parsedResult(
|
|
23430
|
-
await runTaskRequest2(endpoint ?? DEFAULT_SERVE_ENDPOINT, {
|
|
23431
|
-
task: scopeId !== void 0 && task === void 0 ? void 0 : canonicalTask,
|
|
23432
|
-
objective: objective ?? (scopeId === void 0 ? "balanced" : void 0),
|
|
23433
|
-
model,
|
|
23434
|
-
session_id: sessionId,
|
|
23435
|
-
scope_id: scopeId,
|
|
23436
|
-
scope_revision: scopeRevision,
|
|
23437
|
-
context_tokens: contextTokens,
|
|
23438
|
-
required_capabilities: effectiveRequiredCapabilities,
|
|
23439
|
-
prompt,
|
|
23440
|
-
images,
|
|
23441
|
-
messages: taskMessages({ systemPrompt, messages, prompt, images }),
|
|
23442
|
-
input,
|
|
23443
|
-
tools,
|
|
23444
|
-
keep_alive: keepAlive,
|
|
23445
|
-
min_confidence: minConfidence,
|
|
23446
|
-
priority: priority ?? "normal",
|
|
23447
|
-
max_wait_seconds: maxWaitSeconds,
|
|
23448
|
-
timeout_seconds: timeoutSeconds,
|
|
23449
|
-
defer: defer ?? false,
|
|
23450
|
-
execution_preference: executionPreference,
|
|
23451
|
-
min_placement_evidence: minPlacementEvidence,
|
|
23452
|
-
request_options: {
|
|
23453
|
-
format,
|
|
23454
|
-
think,
|
|
23455
|
-
options,
|
|
23456
|
-
logprobs,
|
|
23457
|
-
top_logprobs: topLogprobs
|
|
23458
|
-
}
|
|
23459
|
-
})
|
|
23460
|
-
);
|
|
23461
|
-
if (isStructuredSuccess(result) && result.structuredContent.deferred === true) return result;
|
|
23462
|
-
if (!returnEmbeddings && isStructuredSuccess(result)) {
|
|
23463
|
-
const trimmed = summarizeEmbeddings(result.structuredContent);
|
|
23464
|
-
if (trimmed) return withTaskTelemetry(structuredResult(trimmed));
|
|
23465
|
-
}
|
|
23466
|
-
return withTaskTelemetry(result);
|
|
23603
|
+
if (!existsSync4(file)) continue;
|
|
23604
|
+
const parsed = JSON.parse(readFileSync(file, "utf8"));
|
|
23605
|
+
return parsed.models ?? {};
|
|
23467
23606
|
} catch (error2) {
|
|
23468
|
-
|
|
23607
|
+
process.stderr.write(
|
|
23608
|
+
`freellama-mcp: ignoring unreadable model evidence ${file}: ${error2 instanceof Error ? error2.message : String(error2)}
|
|
23609
|
+
`
|
|
23610
|
+
);
|
|
23469
23611
|
}
|
|
23470
23612
|
}
|
|
23471
|
-
|
|
23472
|
-
|
|
23473
|
-
|
|
23474
|
-
|
|
23475
|
-
|
|
23476
|
-
|
|
23477
|
-
|
|
23478
|
-
|
|
23479
|
-
|
|
23480
|
-
|
|
23481
|
-
|
|
23482
|
-
|
|
23483
|
-
|
|
23484
|
-
},
|
|
23485
|
-
async ({ endpoint, action, jobId, returnEmbeddings }) => {
|
|
23486
|
-
try {
|
|
23487
|
-
if (action === "list" ? jobId !== void 0 : jobId === void 0) {
|
|
23488
|
-
return errorResult(new Error("Use jobId only with get/cancel/remove; all require a jobId."));
|
|
23489
|
-
}
|
|
23490
|
-
if (returnEmbeddings !== void 0 && action !== "get") {
|
|
23491
|
-
return errorResult(new Error("returnEmbeddings is valid only with get."));
|
|
23492
|
-
}
|
|
23493
|
-
const result = parsedResult(action === "list" ? await listTaskJobs2(endpoint) : action === "get" ? await getTaskJob2(endpoint, jobId) : action === "cancel" ? await cancelTaskJob2(endpoint, jobId) : await removeTaskJob2(endpoint, jobId));
|
|
23494
|
-
if (!returnEmbeddings && isStructuredSuccess(result)) {
|
|
23495
|
-
const job = result.structuredContent.job;
|
|
23496
|
-
if (job?.result && typeof job.result === "object") {
|
|
23497
|
-
const trimmed = summarizeEmbeddings(job.result);
|
|
23498
|
-
if (trimmed) return structuredResult({ ...result.structuredContent, job: { ...job, result: trimmed } });
|
|
23499
|
-
}
|
|
23500
|
-
}
|
|
23501
|
-
return result;
|
|
23502
|
-
} catch (error2) {
|
|
23503
|
-
return errorResult(error2);
|
|
23504
|
-
}
|
|
23613
|
+
return {};
|
|
23614
|
+
})();
|
|
23615
|
+
var STRONG_MODEL_HINT = "Re-run with a model graded strong in packages/mcp/evidence/model-evidence.json";
|
|
23616
|
+
function assessDelegatedAnswer(question, evidenceCount, model) {
|
|
23617
|
+
const grounded = evidenceCount > 0;
|
|
23618
|
+
const evidence = MODEL_EVIDENCE[model];
|
|
23619
|
+
if (evidence?.grade === "unusable") {
|
|
23620
|
+
return {
|
|
23621
|
+
recommendation: "escalate",
|
|
23622
|
+
grounded,
|
|
23623
|
+
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.`,
|
|
23624
|
+
measuredBaseRate: `${model}: ${evidence.note}`
|
|
23625
|
+
};
|
|
23505
23626
|
}
|
|
23506
|
-
)
|
|
23507
|
-
|
|
23508
|
-
|
|
23509
|
-
|
|
23510
|
-
|
|
23511
|
-
|
|
23512
|
-
|
|
23513
|
-
maxParallelism: external_exports.number().int().positive().max(64).optional(),
|
|
23514
|
-
endpoint: endpointParam
|
|
23515
|
-
}).strict(),
|
|
23516
|
-
outputSchema: batchResultSchema,
|
|
23517
|
-
annotations: { destructiveHint: false }
|
|
23518
|
-
},
|
|
23519
|
-
async ({ tasks, maxParallelism, endpoint }) => {
|
|
23520
|
-
try {
|
|
23521
|
-
for (const item of tasks) {
|
|
23522
|
-
const task = item.task;
|
|
23523
|
-
if (task.scopeId === void 0 !== (task.scopeRevision === void 0)) {
|
|
23524
|
-
return errorResult(new Error(`batch item ${item.id}: scopeId and scopeRevision must be supplied together.`));
|
|
23525
|
-
}
|
|
23526
|
-
if (task.task === "embedding") {
|
|
23527
|
-
if (task.input === void 0 || task.prompt !== void 0 || task.systemPrompt !== void 0 || task.messages !== void 0 || task.tools !== void 0) {
|
|
23528
|
-
return errorResult(new Error(`batch item ${item.id}: embedding requires input and accepts no chat payload.`));
|
|
23529
|
-
}
|
|
23530
|
-
} else if (task.input !== void 0 || task.prompt === void 0 && task.systemPrompt === void 0 && task.messages === void 0) {
|
|
23531
|
-
return errorResult(new Error(`batch item ${item.id}: chat work requires prompt, systemPrompt, or messages and accepts no input.`));
|
|
23532
|
-
}
|
|
23533
|
-
if (task.images !== void 0 && (task.messages !== void 0 || task.prompt === void 0)) {
|
|
23534
|
-
return errorResult(new Error(`batch item ${item.id}: top-level images requires prompt without messages; put images inside messages instead.`));
|
|
23535
|
-
}
|
|
23536
|
-
if (task.images !== void 0 && task.model === void 0) {
|
|
23537
|
-
return errorResult(new Error(`batch item ${item.id}: images require an explicit tested vision model.`));
|
|
23538
|
-
}
|
|
23539
|
-
}
|
|
23540
|
-
return withBatchTelemetry(parsedResult(await runTaskBatchRequest2(endpoint ?? DEFAULT_SERVE_ENDPOINT, {
|
|
23541
|
-
tasks: tasks.map((item) => {
|
|
23542
|
-
const task = item.task;
|
|
23543
|
-
return {
|
|
23544
|
-
id: item.id,
|
|
23545
|
-
independent: item.independent,
|
|
23546
|
-
task: {
|
|
23547
|
-
task: task.task === "code_review" ? "coding" : task.task ?? (task.scopeId === void 0 ? "completion" : void 0),
|
|
23548
|
-
objective: task.objective ?? (task.scopeId === void 0 ? "balanced" : void 0),
|
|
23549
|
-
model: task.model,
|
|
23550
|
-
session_id: task.sessionId,
|
|
23551
|
-
scope_id: task.scopeId,
|
|
23552
|
-
scope_revision: task.scopeRevision,
|
|
23553
|
-
context_tokens: task.contextTokens,
|
|
23554
|
-
execution_preference: task.executionPreference,
|
|
23555
|
-
min_placement_evidence: task.minPlacementEvidence,
|
|
23556
|
-
required_capabilities: task.requiredCapabilities,
|
|
23557
|
-
priority: task.priority ?? "normal",
|
|
23558
|
-
max_wait_seconds: task.maxWaitSeconds,
|
|
23559
|
-
timeout_seconds: task.timeoutSeconds,
|
|
23560
|
-
prompt: task.prompt,
|
|
23561
|
-
images: task.images,
|
|
23562
|
-
messages: taskMessages(task),
|
|
23563
|
-
input: task.input,
|
|
23564
|
-
tools: task.tools,
|
|
23565
|
-
keep_alive: task.keepAlive,
|
|
23566
|
-
min_confidence: task.minConfidence,
|
|
23567
|
-
request_options: {
|
|
23568
|
-
format: task.format,
|
|
23569
|
-
think: task.think,
|
|
23570
|
-
options: task.options,
|
|
23571
|
-
logprobs: task.logprobs,
|
|
23572
|
-
top_logprobs: task.topLogprobs
|
|
23573
|
-
}
|
|
23574
|
-
}
|
|
23575
|
-
};
|
|
23576
|
-
}),
|
|
23577
|
-
max_parallelism: maxParallelism
|
|
23578
|
-
})));
|
|
23579
|
-
} catch (error2) {
|
|
23580
|
-
return errorResult(error2);
|
|
23581
|
-
}
|
|
23627
|
+
if (!evidence) {
|
|
23628
|
+
return {
|
|
23629
|
+
recommendation: "verify",
|
|
23630
|
+
grounded,
|
|
23631
|
+
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.`,
|
|
23632
|
+
measuredBaseRate: "no measured base rate for this model"
|
|
23633
|
+
};
|
|
23582
23634
|
}
|
|
23583
|
-
)
|
|
23584
|
-
|
|
23585
|
-
|
|
23586
|
-
|
|
23587
|
-
|
|
23588
|
-
|
|
23589
|
-
|
|
23590
|
-
model: external_exports.string().min(1),
|
|
23591
|
-
ollamaEndpoint: ollamaEndpointParam,
|
|
23592
|
-
timeoutSeconds: external_exports.number().int().positive().optional()
|
|
23593
|
-
}).strict(),
|
|
23594
|
-
outputSchema: manageResultSchema,
|
|
23595
|
-
annotations: { destructiveHint: false }
|
|
23596
|
-
},
|
|
23597
|
-
async ({ action, model, ollamaEndpoint, timeoutSeconds }, extra) => {
|
|
23598
|
-
try {
|
|
23599
|
-
if (action === "stop" && timeoutSeconds !== void 0) {
|
|
23600
|
-
return errorResult(new Error('`timeoutSeconds` is valid only for action "pull".'));
|
|
23601
|
-
}
|
|
23602
|
-
const data = action === "pull" ? await ollamaPull(ollamaEndpoint, model, timeoutSeconds, {
|
|
23603
|
-
signal: extra.signal,
|
|
23604
|
-
onProgress: pullProgressReporter(extra)
|
|
23605
|
-
}) : await ollamaFetch(ollamaEndpoint, "/api/generate", {
|
|
23606
|
-
method: "POST",
|
|
23607
|
-
body: { model, keep_alive: 0 },
|
|
23608
|
-
signal: extra.signal
|
|
23609
|
-
});
|
|
23610
|
-
const payload = data;
|
|
23611
|
-
if (typeof payload.error === "string" && payload.error) {
|
|
23612
|
-
return errorResult(new Error(`${action} ${model} failed: ${payload.error}`));
|
|
23613
|
-
}
|
|
23614
|
-
return structuredResult(payload);
|
|
23615
|
-
} catch (error2) {
|
|
23616
|
-
return errorResult(error2);
|
|
23617
|
-
}
|
|
23635
|
+
if (evidence.grade === "weak" && grounded) {
|
|
23636
|
+
return {
|
|
23637
|
+
recommendation: "verify",
|
|
23638
|
+
grounded,
|
|
23639
|
+
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.`,
|
|
23640
|
+
measuredBaseRate: `${model}: ${evidence.note}`
|
|
23641
|
+
};
|
|
23618
23642
|
}
|
|
23619
|
-
)
|
|
23620
|
-
|
|
23621
|
-
|
|
23622
|
-
|
|
23623
|
-
|
|
23624
|
-
|
|
23625
|
-
|
|
23626
|
-
|
|
23627
|
-
|
|
23628
|
-
|
|
23629
|
-
|
|
23630
|
-
|
|
23631
|
-
|
|
23643
|
+
if (!grounded) {
|
|
23644
|
+
return {
|
|
23645
|
+
recommendation: "escalate",
|
|
23646
|
+
grounded: false,
|
|
23647
|
+
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.",
|
|
23648
|
+
measuredBaseRate: "ungrounded answers have no measured accuracy \u2014 they were never the tested path"
|
|
23649
|
+
};
|
|
23650
|
+
}
|
|
23651
|
+
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;
|
|
23652
|
+
if (judgmentSignals.test(question)) {
|
|
23653
|
+
return {
|
|
23654
|
+
recommendation: "verify",
|
|
23655
|
+
grounded: true,
|
|
23656
|
+
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.",
|
|
23657
|
+
measuredBaseRate: `${model}: ${evidence.note}; measured on lookups, not judgment calls`
|
|
23658
|
+
};
|
|
23659
|
+
}
|
|
23660
|
+
if (evidenceCount > 5) {
|
|
23661
|
+
return {
|
|
23662
|
+
recommendation: "verify",
|
|
23663
|
+
grounded: true,
|
|
23664
|
+
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.`,
|
|
23665
|
+
measuredBaseRate: `${model}: ${evidence.note}; measured within a 1-5 file scope`
|
|
23666
|
+
};
|
|
23667
|
+
}
|
|
23668
|
+
return {
|
|
23669
|
+
recommendation: "accept",
|
|
23670
|
+
grounded: true,
|
|
23671
|
+
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}).`,
|
|
23672
|
+
measuredBaseRate: `${model}: ${evidence.note}`
|
|
23673
|
+
};
|
|
23674
|
+
}
|
|
23675
|
+
function extractExistingWorkspacePath(command, workspace) {
|
|
23676
|
+
let root;
|
|
23677
|
+
try {
|
|
23678
|
+
root = realpathSync(workspace);
|
|
23679
|
+
} catch {
|
|
23680
|
+
return null;
|
|
23681
|
+
}
|
|
23682
|
+
const tokens = command.match(/(?:[^\s"'\\]+|"(?:\\.|[^"])*"|'[^']*')+/g) ?? [];
|
|
23683
|
+
const patternFlags = /* @__PURE__ */ new Set(["-name", "-iname", "-path", "-ipath", "-regex", "-iregex", "-wholename"]);
|
|
23684
|
+
for (let index = 0; index < tokens.length; index += 1) {
|
|
23685
|
+
const raw = tokens[index] ?? "";
|
|
23686
|
+
if (index > 0 && patternFlags.has(tokens[index - 1] ?? "")) continue;
|
|
23687
|
+
const token = raw.replace(/^["']|["']$/g, "").replace(/[|;&)]+$/g, "").replace(/:\d+(?::\d+)?$/, "");
|
|
23688
|
+
if (!token || token.startsWith("-") || /[*?$`]/.test(token)) continue;
|
|
23689
|
+
const candidate = path4.isAbsolute(token) ? token : path4.resolve(root, token);
|
|
23690
|
+
if (!existsSync4(candidate)) continue;
|
|
23632
23691
|
try {
|
|
23633
|
-
|
|
23634
|
-
|
|
23635
|
-
|
|
23636
|
-
|
|
23637
|
-
|
|
23638
|
-
} catch (error2) {
|
|
23639
|
-
return errorResult(error2);
|
|
23692
|
+
const resolved = realpathSync(candidate);
|
|
23693
|
+
const relative = path4.relative(root, resolved);
|
|
23694
|
+
const withinRoot = relative && relative !== ".." && !relative.startsWith(`..${path4.sep}`);
|
|
23695
|
+
if (withinRoot && statSync(resolved).isFile()) return relative;
|
|
23696
|
+
} catch {
|
|
23640
23697
|
}
|
|
23641
23698
|
}
|
|
23642
|
-
|
|
23643
|
-
|
|
23699
|
+
return null;
|
|
23700
|
+
}
|
|
23701
|
+
var adapterCallSchema = external_exports.object({
|
|
23702
|
+
raw_name: external_exports.string().optional(),
|
|
23703
|
+
status: external_exports.string().optional(),
|
|
23704
|
+
arguments: external_exports.object({
|
|
23705
|
+
tool: external_exports.string().optional(),
|
|
23706
|
+
command: external_exports.string().optional(),
|
|
23707
|
+
queries: external_exports.object({ path: external_exports.string().optional() }).passthrough().optional()
|
|
23708
|
+
}).passthrough().optional()
|
|
23709
|
+
}).passthrough();
|
|
23710
|
+
var adapterResultSchema = external_exports.object({
|
|
23711
|
+
final_answer: external_exports.string(),
|
|
23712
|
+
tool_calls: external_exports.array(adapterCallSchema).default([]),
|
|
23713
|
+
usage: external_exports.object({
|
|
23714
|
+
input_tokens: external_exports.number().nullable().optional(),
|
|
23715
|
+
output_tokens: external_exports.number().nullable().optional()
|
|
23716
|
+
}).passthrough().default({}),
|
|
23717
|
+
model_metadata: external_exports.object({
|
|
23718
|
+
context_compactions: external_exports.number().int().nonnegative().optional(),
|
|
23719
|
+
runtime_config: external_exports.record(external_exports.string(), external_exports.unknown()).optional(),
|
|
23720
|
+
context_management: external_exports.object({
|
|
23721
|
+
token_counting: external_exports.enum(["configured_estimate", "model_calibrated_estimate"]),
|
|
23722
|
+
estimate_scale: external_exports.number().positive(),
|
|
23723
|
+
calibration_samples: external_exports.number().int().nonnegative(),
|
|
23724
|
+
pinned_overflow: external_exports.enum(["error", "clip"]),
|
|
23725
|
+
compactions: external_exports.number().int().nonnegative()
|
|
23726
|
+
}).passthrough().optional()
|
|
23727
|
+
}).passthrough().optional()
|
|
23728
|
+
});
|
|
23729
|
+
function parseAdapterResult(raw) {
|
|
23730
|
+
return adapterResultSchema.parse(JSON.parse(raw));
|
|
23731
|
+
}
|
|
23732
|
+
|
|
23733
|
+
// src/tools/delegate-research.ts
|
|
23734
|
+
var researchResultSchema = external_exports.object({
|
|
23735
|
+
answer: external_exports.string(),
|
|
23736
|
+
summary: external_exports.string(),
|
|
23737
|
+
citations: external_exports.unknown().optional(),
|
|
23738
|
+
verification: external_exports.unknown(),
|
|
23739
|
+
usage: external_exports.unknown().optional(),
|
|
23740
|
+
telemetry: external_exports.unknown().optional()
|
|
23741
|
+
}).passthrough();
|
|
23742
|
+
async function routedResearchModel(endpoint) {
|
|
23644
23743
|
const decision = JSON.parse(
|
|
23645
|
-
await route2(endpoint, "coding", "balanced", void 0, void 0, void 0, [], void 0,
|
|
23744
|
+
await route2(endpoint, "coding", "balanced", void 0, void 0, void 0, [], void 0, void 0, void 0)
|
|
23646
23745
|
);
|
|
23647
23746
|
if (typeof decision.selected_model !== "string" || !decision.selected_model) {
|
|
23648
23747
|
throw new Error(
|
|
@@ -23651,196 +23750,202 @@ async function routedResearchModel(endpoint, executionPreference, minPlacementEv
|
|
|
23651
23750
|
}
|
|
23652
23751
|
return decision.selected_model;
|
|
23653
23752
|
}
|
|
23654
|
-
|
|
23655
|
-
|
|
23656
|
-
|
|
23657
|
-
|
|
23658
|
-
|
|
23659
|
-
|
|
23660
|
-
|
|
23661
|
-
|
|
23662
|
-
|
|
23663
|
-
|
|
23664
|
-
|
|
23665
|
-
|
|
23666
|
-
|
|
23667
|
-
|
|
23668
|
-
|
|
23669
|
-
|
|
23670
|
-
|
|
23671
|
-
|
|
23672
|
-
|
|
23673
|
-
|
|
23674
|
-
|
|
23675
|
-
|
|
23676
|
-
|
|
23677
|
-
|
|
23678
|
-
}).strict(),
|
|
23679
|
-
outputSchema: researchResultSchema,
|
|
23680
|
-
annotations: { destructiveHint: false }
|
|
23681
|
-
},
|
|
23682
|
-
async ({ question, workspacePath, adapter, model, endpoint, executionPreference, minPlacementEvidence, legacyText, agent }, extra) => {
|
|
23683
|
-
const chosenAdapter = adapter ?? DEFAULT_RESEARCH_ADAPTER;
|
|
23684
|
-
let resolvedWorkspace;
|
|
23685
|
-
let chosenModel;
|
|
23686
|
-
try {
|
|
23687
|
-
resolvedWorkspace = await assertAllowedWorkspace(workspacePath);
|
|
23688
|
-
chosenModel = model ?? DEFAULT_DELEGATE_MODEL ?? await routedResearchModel(endpoint, executionPreference, minPlacementEvidence);
|
|
23689
|
-
} catch (error2) {
|
|
23690
|
-
return researchErrorResult(error2);
|
|
23691
|
-
}
|
|
23692
|
-
const known = MODEL_EVIDENCE[chosenModel];
|
|
23693
|
-
if (known?.grade === "unusable") {
|
|
23694
|
-
return structuredResult({
|
|
23695
|
-
adapter: chosenAdapter,
|
|
23696
|
-
verification: assessDelegatedAnswer(question, 0, chosenModel),
|
|
23697
|
-
answer: "",
|
|
23698
|
-
toolCallCount: 0,
|
|
23699
|
-
usage: { inputTokens: null, outputTokens: null },
|
|
23700
|
-
telemetry: costTelemetry({ inputTokens: null, outputTokens: null }, EXTERNAL_COST),
|
|
23701
|
-
evidence: [],
|
|
23702
|
-
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.`
|
|
23703
|
-
});
|
|
23704
|
-
}
|
|
23705
|
-
const dir = await mkdtemp(path5.join(tmpdir(), "freellama-delegate-"));
|
|
23706
|
-
const promptFile = path5.join(dir, "prompt.md");
|
|
23707
|
-
const resultFile = path5.join(dir, "result.json");
|
|
23708
|
-
try {
|
|
23709
|
-
await writeFile(promptFile, `${question}
|
|
23710
|
-
`, "utf8");
|
|
23711
|
-
const adapter2 = RESEARCH_ADAPTERS[chosenAdapter];
|
|
23712
|
-
if (!existsSync5(adapter2)) {
|
|
23713
|
-
return researchErrorResult(
|
|
23714
|
-
new Error(
|
|
23715
|
-
`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.`
|
|
23716
|
-
)
|
|
23717
|
-
);
|
|
23718
|
-
}
|
|
23719
|
-
const running = execFileAsync("python3", [adapter2], {
|
|
23720
|
-
signal: extra.signal,
|
|
23721
|
-
env: {
|
|
23722
|
-
...delegateEnvironment(),
|
|
23723
|
-
FREELLAMA_TARGET_MODEL: chosenModel,
|
|
23724
|
-
FREELLAMA_AGENT_MANAGED_ENDPOINT: endpoint ?? DEFAULT_SERVE_ENDPOINT,
|
|
23725
|
-
FREELLAMA_AGENT_EXECUTION_PREFERENCE: executionPreference ?? "auto",
|
|
23726
|
-
FREELLAMA_AGENT_MIN_PLACEMENT_EVIDENCE: minPlacementEvidence ?? "configured",
|
|
23727
|
-
FREELLAMA_AGENT_TOKEN_CALIBRATION_DIR: DEFAULT_TOKEN_CALIBRATION_DIR,
|
|
23728
|
-
FREELLAMA_BENCH_WORKSPACE: resolvedWorkspace,
|
|
23729
|
-
FREELLAMA_BENCH_PROMPT: promptFile,
|
|
23730
|
-
FREELLAMA_AGENT_RESULT: resultFile,
|
|
23731
|
-
FREELLAMA_AGENT_MAX_TURNS: String(agent?.maxTurns ?? DEFAULT_DELEGATE_MAX_TURNS),
|
|
23732
|
-
...agent?.contextTokens !== void 0 ? { FREELLAMA_AGENT_NUM_CTX: String(agent.contextTokens) } : {},
|
|
23733
|
-
...agent?.outputTokens !== void 0 ? { FREELLAMA_AGENT_NUM_PREDICT: String(agent.outputTokens) } : {},
|
|
23734
|
-
...agent?.temperature !== void 0 ? { FREELLAMA_AGENT_TEMPERATURE: String(agent.temperature) } : {},
|
|
23735
|
-
...agent?.seed !== void 0 ? { FREELLAMA_AGENT_SEED: String(agent.seed) } : {},
|
|
23736
|
-
...agent?.think !== void 0 ? { FREELLAMA_AGENT_THINK: String(agent.think) } : {},
|
|
23737
|
-
...agent?.keepAlive !== void 0 ? { FREELLAMA_AGENT_KEEP_ALIVE: agent.keepAlive } : {},
|
|
23738
|
-
...agent?.requestTimeoutSeconds !== void 0 ? { FREELLAMA_AGENT_REQUEST_TIMEOUT_SECONDS: String(agent.requestTimeoutSeconds) } : {},
|
|
23739
|
-
...agent?.toolTimeoutSeconds !== void 0 ? { FREELLAMA_AGENT_TOOL_TIMEOUT_SECONDS: String(agent.toolTimeoutSeconds) } : {}
|
|
23740
|
-
},
|
|
23741
|
-
timeout: DEFAULT_DELEGATE_TIMEOUT_SECONDS * 1e3,
|
|
23742
|
-
// The answer is read from `resultFile`, never from stdout — but execFile's default 1 MB
|
|
23743
|
-
// maxBuffer still applies to the agent's own progress logging, and overflowing it kills
|
|
23744
|
-
// the subprocess and surfaces as a research failure with no explanation. Give the logs
|
|
23745
|
-
// room; nothing here is proportional to the size of the answer.
|
|
23746
|
-
maxBuffer: 32 * 1024 * 1024,
|
|
23747
|
-
// On timeout, don't negotiate: SIGTERM can be swallowed by a python process blocked in a
|
|
23748
|
-
// long model call, which would leave the child (and its VRAM) alive past the deadline the
|
|
23749
|
-
// caller was promised.
|
|
23750
|
-
killSignal: "SIGKILL"
|
|
23751
|
-
});
|
|
23752
|
-
liveDelegates.add(running.child);
|
|
23753
|
-
let adapterError = null;
|
|
23753
|
+
function registerDelegateResearchTool(server2) {
|
|
23754
|
+
server2.registerTool(
|
|
23755
|
+
"delegate_research",
|
|
23756
|
+
{
|
|
23757
|
+
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.",
|
|
23758
|
+
inputSchema: external_exports.object({
|
|
23759
|
+
question: external_exports.string().min(1),
|
|
23760
|
+
workspacePath: external_exports.string().min(1),
|
|
23761
|
+
adapter: external_exports.enum(["bash", "octocode"]).optional(),
|
|
23762
|
+
model: external_exports.string().min(1).optional(),
|
|
23763
|
+
endpoint: endpointParam,
|
|
23764
|
+
agent: external_exports.object({
|
|
23765
|
+
maxTurns: external_exports.number().int().positive().optional(),
|
|
23766
|
+
contextTokens: external_exports.number().int().positive().optional(),
|
|
23767
|
+
outputTokens: external_exports.number().int().positive().optional()
|
|
23768
|
+
}).strict().optional()
|
|
23769
|
+
}).strict(),
|
|
23770
|
+
outputSchema: researchResultSchema,
|
|
23771
|
+
annotations: { destructiveHint: false }
|
|
23772
|
+
},
|
|
23773
|
+
async ({ question, workspacePath, adapter, model, endpoint, agent }, extra) => {
|
|
23774
|
+
const chosenAdapter = adapter ?? DEFAULT_RESEARCH_ADAPTER;
|
|
23775
|
+
let resolvedWorkspace;
|
|
23776
|
+
let chosenModel;
|
|
23754
23777
|
try {
|
|
23755
|
-
await
|
|
23778
|
+
resolvedWorkspace = await assertAllowedWorkspace(workspacePath);
|
|
23779
|
+
chosenModel = model ?? DEFAULT_DELEGATE_MODEL ?? await routedResearchModel(endpoint);
|
|
23756
23780
|
} catch (error2) {
|
|
23757
|
-
|
|
23758
|
-
} finally {
|
|
23759
|
-
liveDelegates.delete(running.child);
|
|
23781
|
+
return researchErrorResult(error2);
|
|
23760
23782
|
}
|
|
23761
|
-
|
|
23762
|
-
|
|
23763
|
-
|
|
23764
|
-
|
|
23765
|
-
|
|
23766
|
-
|
|
23767
|
-
|
|
23768
|
-
|
|
23769
|
-
|
|
23783
|
+
const known = MODEL_EVIDENCE[chosenModel];
|
|
23784
|
+
if (known?.grade === "unusable") {
|
|
23785
|
+
return structuredResult({
|
|
23786
|
+
adapter: chosenAdapter,
|
|
23787
|
+
verification: assessDelegatedAnswer(question, 0, chosenModel),
|
|
23788
|
+
answer: "",
|
|
23789
|
+
toolCallCount: 0,
|
|
23790
|
+
usage: { inputTokens: null, outputTokens: null },
|
|
23791
|
+
telemetry: costTelemetry({ inputTokens: null, outputTokens: null }, EXTERNAL_COST),
|
|
23792
|
+
evidence: [],
|
|
23793
|
+
summary: `Refused before running: ${chosenModel} is measured unusable for research here (${known.note}). ${STRONG_MODEL_HINT}, or answer it yourself.`
|
|
23794
|
+
});
|
|
23770
23795
|
}
|
|
23771
|
-
const
|
|
23772
|
-
|
|
23773
|
-
|
|
23774
|
-
|
|
23775
|
-
|
|
23776
|
-
|
|
23777
|
-
|
|
23778
|
-
|
|
23779
|
-
|
|
23780
|
-
|
|
23781
|
-
|
|
23782
|
-
|
|
23783
|
-
|
|
23784
|
-
|
|
23785
|
-
|
|
23786
|
-
|
|
23787
|
-
|
|
23788
|
-
|
|
23789
|
-
|
|
23790
|
-
|
|
23791
|
-
|
|
23792
|
-
|
|
23793
|
-
|
|
23794
|
-
|
|
23796
|
+
const dir = await mkdtemp(path5.join(tmpdir(), "freellama-delegate-"));
|
|
23797
|
+
const promptFile = path5.join(dir, "prompt.md");
|
|
23798
|
+
const resultFile = path5.join(dir, "result.json");
|
|
23799
|
+
try {
|
|
23800
|
+
await writeFile(promptFile, `${question}
|
|
23801
|
+
`, "utf8");
|
|
23802
|
+
const adapter2 = RESEARCH_ADAPTERS[chosenAdapter];
|
|
23803
|
+
if (!existsSync5(adapter2)) {
|
|
23804
|
+
return researchErrorResult(
|
|
23805
|
+
new Error(
|
|
23806
|
+
`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.`
|
|
23807
|
+
)
|
|
23808
|
+
);
|
|
23809
|
+
}
|
|
23810
|
+
const running = runDelegate(process.execPath, [adapter2], {
|
|
23811
|
+
signal: extra.signal,
|
|
23812
|
+
env: {
|
|
23813
|
+
...delegateEnvironment(),
|
|
23814
|
+
FREELLAMA_TARGET_MODEL: chosenModel,
|
|
23815
|
+
FREELLAMA_AGENT_MANAGED_ENDPOINT: endpoint ?? DEFAULT_SERVE_ENDPOINT,
|
|
23816
|
+
FREELLAMA_AGENT_TOKEN_CALIBRATION_DIR: DEFAULT_TOKEN_CALIBRATION_DIR,
|
|
23817
|
+
FREELLAMA_BENCH_WORKSPACE: resolvedWorkspace,
|
|
23818
|
+
FREELLAMA_BENCH_PROMPT: promptFile,
|
|
23819
|
+
FREELLAMA_AGENT_RESULT: resultFile,
|
|
23820
|
+
FREELLAMA_AGENT_MAX_TURNS: String(agent?.maxTurns ?? DEFAULT_DELEGATE_MAX_TURNS),
|
|
23821
|
+
...agent?.contextTokens !== void 0 ? { FREELLAMA_AGENT_NUM_CTX: String(agent.contextTokens) } : {},
|
|
23822
|
+
...agent?.outputTokens !== void 0 ? { FREELLAMA_AGENT_NUM_PREDICT: String(agent.outputTokens) } : {}
|
|
23823
|
+
},
|
|
23824
|
+
timeoutMs: DEFAULT_DELEGATE_TIMEOUT_SECONDS * 1e3,
|
|
23825
|
+
// The answer is read from `resultFile`, never from stdout — but execFile's usual 1 MB
|
|
23826
|
+
// maxBuffer still applies to the agent's own progress logging, and overflowing it kills
|
|
23827
|
+
// the subprocess and surfaces as a research failure with no explanation. Give the logs
|
|
23828
|
+
// room; nothing here is proportional to the size of the answer.
|
|
23829
|
+
maxBuffer: 32 * 1024 * 1024
|
|
23830
|
+
// On timeout, abort, or overflow runDelegate SIGKILLs the whole process group: SIGTERM can
|
|
23831
|
+
// be swallowed by an adapter blocked in a long model call, and killing the adapter alone
|
|
23832
|
+
// would orphan `npx octocode` and sandbox shells.
|
|
23833
|
+
});
|
|
23834
|
+
liveDelegates.add(running.child);
|
|
23835
|
+
let adapterError = null;
|
|
23836
|
+
try {
|
|
23837
|
+
await running.done;
|
|
23838
|
+
} catch (error2) {
|
|
23839
|
+
adapterError = error2;
|
|
23840
|
+
} finally {
|
|
23841
|
+
liveDelegates.delete(running.child);
|
|
23842
|
+
killDelegateTree(running.child);
|
|
23843
|
+
}
|
|
23844
|
+
let result;
|
|
23845
|
+
try {
|
|
23846
|
+
result = parseAdapterResult(await readFile2(resultFile, "utf8"));
|
|
23847
|
+
} catch {
|
|
23848
|
+
return researchErrorResult(
|
|
23849
|
+
adapterError ?? new Error(
|
|
23850
|
+
`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.`
|
|
23851
|
+
)
|
|
23852
|
+
);
|
|
23853
|
+
}
|
|
23854
|
+
const evidence = result.tool_calls.map((call, index) => {
|
|
23855
|
+
const target = call.arguments?.queries?.path;
|
|
23856
|
+
const rawDetail = call.arguments?.command ?? null;
|
|
23857
|
+
const detail = rawDetail ? clipText(rawDetail, 400) : null;
|
|
23858
|
+
return {
|
|
23859
|
+
step: index + 1,
|
|
23860
|
+
tool: call.arguments?.tool ?? call.raw_name ?? "?",
|
|
23861
|
+
// The adapters record "ok" | "error" | "repeat" per call. Carrying it through is what
|
|
23862
|
+
// lets a reader tell a run that read three files from one that failed three commands —
|
|
23863
|
+
// indistinguishable in the trail before, and graded identically.
|
|
23864
|
+
status: call.status ?? "ok",
|
|
23865
|
+
path: target ? path5.relative(resolvedWorkspace, target) : detail ? extractExistingWorkspacePath(detail, resolvedWorkspace) : null,
|
|
23866
|
+
detail,
|
|
23867
|
+
detail_truncated: rawDetail !== detail
|
|
23868
|
+
};
|
|
23869
|
+
});
|
|
23870
|
+
const succeeded = evidence.filter((step) => step.status === "ok");
|
|
23871
|
+
const failed = evidence.length - succeeded.length;
|
|
23872
|
+
const evidenceText = evidence.map(
|
|
23873
|
+
(step) => ` ${step.step}. ${step.tool}${step.status === "ok" ? "" : ` [${step.status}]`}${step.path ? ` -> ${step.path}` : ""}${step.detail ? `: ${step.detail}` : ""}`
|
|
23874
|
+
).join("\n");
|
|
23875
|
+
const citations = succeeded.map((step) => ({ step: step.step, tool: step.tool, path: step.path }));
|
|
23876
|
+
if (adapterError) {
|
|
23877
|
+
const diagnostic = `research adapter failed: ${result.final_answer}` + (evidenceText ? `
|
|
23795
23878
|
Evidence collected before the failure:
|
|
23796
23879
|
${evidenceText}` : "");
|
|
23797
|
-
|
|
23798
|
-
|
|
23799
|
-
|
|
23800
|
-
|
|
23801
|
-
|
|
23802
|
-
|
|
23803
|
-
|
|
23804
|
-
|
|
23805
|
-
|
|
23806
|
-
|
|
23807
|
-
|
|
23808
|
-
|
|
23809
|
-
|
|
23810
|
-
|
|
23811
|
-
|
|
23812
|
-
|
|
23813
|
-
|
|
23814
|
-
|
|
23815
|
-
|
|
23816
|
-
|
|
23817
|
-
|
|
23818
|
-
|
|
23819
|
-
|
|
23820
|
-
|
|
23821
|
-
|
|
23822
|
-
|
|
23823
|
-
|
|
23824
|
-
|
|
23825
|
-
|
|
23826
|
-
|
|
23827
|
-
|
|
23828
|
-
|
|
23829
|
-
summary
|
|
23830
|
-
};
|
|
23831
|
-
const cited = [...new Set(citations.map((citation) => citation.path).filter(Boolean))];
|
|
23832
|
-
const answerText = `${clipText(result.final_answer, ANSWER_TEXT_MAX_CHARS)}
|
|
23880
|
+
const refusal = external_exports.object({ receipt: external_exports.object({ error: external_exports.string() }).passthrough() }).safeParse(result.model_metadata?.transport_error);
|
|
23881
|
+
if (refusal.success) {
|
|
23882
|
+
return researchErrorResult(new Error(JSON.stringify(refusal.data.receipt)), diagnostic);
|
|
23883
|
+
}
|
|
23884
|
+
return researchErrorResult(new Error(diagnostic));
|
|
23885
|
+
}
|
|
23886
|
+
const verification = assessDelegatedAnswer(question, succeeded.length, chosenModel);
|
|
23887
|
+
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.`;
|
|
23888
|
+
const payload = {
|
|
23889
|
+
adapter: chosenAdapter,
|
|
23890
|
+
verification,
|
|
23891
|
+
answer: result.final_answer,
|
|
23892
|
+
// recommendation + why live under `verification`; `citations` completes the triple so a
|
|
23893
|
+
// caller never has to read `summary` to act on the result.
|
|
23894
|
+
citations,
|
|
23895
|
+
toolCallCount: result.tool_calls.length,
|
|
23896
|
+
successfulToolCallCount: succeeded.length,
|
|
23897
|
+
usage: {
|
|
23898
|
+
inputTokens: result.usage.input_tokens,
|
|
23899
|
+
outputTokens: result.usage.output_tokens
|
|
23900
|
+
},
|
|
23901
|
+
telemetry: costTelemetry({
|
|
23902
|
+
inputTokens: result.usage.input_tokens ?? null,
|
|
23903
|
+
outputTokens: result.usage.output_tokens ?? null
|
|
23904
|
+
}, EXTERNAL_COST),
|
|
23905
|
+
contextManagement: result.model_metadata?.context_management ?? null,
|
|
23906
|
+
execution: { receipts: result.model_metadata?.execution_receipts ?? [] },
|
|
23907
|
+
evidence,
|
|
23908
|
+
summary
|
|
23909
|
+
};
|
|
23910
|
+
const cited = [...new Set(citations.map((citation) => citation.path).filter(Boolean))];
|
|
23911
|
+
const answerText = `${clipText(result.final_answer, ANSWER_TEXT_MAX_CHARS)}
|
|
23833
23912
|
|
|
23834
23913
|
verification: ${verification.recommendation}` + (cited.length ? `
|
|
23835
23914
|
cited: ${cited.slice(0, 20).join(", ")}` : "") + `
|
|
23836
23915
|
(${chosenModel}, ${result.tool_calls.length} tool call(s))`;
|
|
23837
|
-
|
|
23838
|
-
|
|
23839
|
-
|
|
23840
|
-
|
|
23841
|
-
|
|
23916
|
+
return structuredResult(payload, { text: answerText });
|
|
23917
|
+
} catch (error2) {
|
|
23918
|
+
return researchErrorResult(error2);
|
|
23919
|
+
} finally {
|
|
23920
|
+
await rm(dir, { recursive: true, force: true });
|
|
23921
|
+
}
|
|
23842
23922
|
}
|
|
23843
|
-
|
|
23923
|
+
);
|
|
23924
|
+
}
|
|
23925
|
+
|
|
23926
|
+
// src/index.ts
|
|
23927
|
+
installProcessLifecycle();
|
|
23928
|
+
var INSTRUCTIONS = `models{view:"installed"}; models{view:"resident"}.
|
|
23929
|
+
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.
|
|
23930
|
+
quality objective needs a task policy or explicit model; medium confidence needs policy+benchmark; observed placement needs /api/ps proof.
|
|
23931
|
+
Scoped run_task needs scopeId+scopeRevision. defer:true: job.id\u2192task_jobs.jobId unchanged. Restart loses scopes/sessions/jobs.
|
|
23932
|
+
Check isError first; prefer structuredContent, else content[].text. page.next_cursor\u2192cursor unchanged.
|
|
23933
|
+
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.
|
|
23934
|
+
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.
|
|
23935
|
+
serve :11435; default-endpoint autostart optional. Docs: freellama://docs/index.`;
|
|
23936
|
+
var server = new McpServer(
|
|
23937
|
+
{ name: "freellama", version: SERVER_VERSION },
|
|
23938
|
+
{ instructions: INSTRUCTIONS }
|
|
23844
23939
|
);
|
|
23845
|
-
|
|
23940
|
+
registerDocsResources(server);
|
|
23941
|
+
registerDoctorTool(server);
|
|
23942
|
+
registerSessionTool(server);
|
|
23943
|
+
registerModelsTool(server);
|
|
23944
|
+
registerScopeTool(server);
|
|
23945
|
+
registerRunTaskTool(server);
|
|
23946
|
+
registerTaskJobsTool(server);
|
|
23947
|
+
registerOllamaLifecycleTools(server);
|
|
23948
|
+
registerDelegateResearchTool(server);
|
|
23949
|
+
var transport = withoutSchemaDialect(new StdioServerTransport());
|
|
23846
23950
|
await server.connect(transport);
|
|
23951
|
+
startServeInBackground();
|