Project Files
src / toolsProvider.ts
// src/toolsProvider.ts
import { type Tool, type ToolsProviderController } from "@lmstudio/sdk";
import { globalConfigSchematics } from "./config.js";
import { createReviewImageTool, createReviewSequenceTool, getLogsDir } from "./core-bundle.mjs";
import { createFindDocTool } from "./tools/find_doc.js";
import { createShowImageTool } from "./tools/show_image.js";
import { createExportDocTool } from "./tools/export_doc.js";
import { createReadDocTool } from "./tools/read_doc.js";
import { createFetchImageTool } from "./tools/fetch_image.js";
import { createMemorizeDocTool } from "./tools/memorize_doc.js";
import { createRewriteDocTool } from "./tools/rewrite_doc.js";
import { createForgetDocTool } from "./tools/forget_doc.js";
import { createReadConfigTool } from "./tools/read_config.js";
import { createSkipDocTool } from "./tools/skip_doc.js";
import { createAnalyseImageTool } from "./tools/analyse_image.js";
import { createAnnotateImageTool } from "./tools/annotate_image.js";
import { createExtractImageTool } from "./tools/extract_image.js";
import fs from "fs";
import path from "path";
// ensure consistent timestamp with timezone across logs
function localTimestamp(): string {
try {
return new Date().toLocaleString(undefined, {
year: "numeric",
month: "2-digit",
day: "2-digit",
hour: "2-digit",
minute: "2-digit",
second: "2-digit",
hour12: false,
timeZoneName: "short",
});
} catch {
return new Date().toString();
}
}
let lastConfigSnapshotKey: string | null = null;
export async function toolsProvider(ctl: ToolsProviderController) {
// Export selected config values to process.env so underlying services can read them.
try {
const getter: any =
(ctl as any).getGlobalPluginConfig || (ctl as any).getGlobalConfig;
const gcfg = getter ? getter.call(ctl, globalConfigSchematics) : null;
const preview = gcfg.get("PREVIEW_IN_CHAT");
const httpPort = gcfg.get("HTTP_SERVER_PORT");
const baseUrl = gcfg.get("baseUrl");
if (typeof baseUrl === "string" && baseUrl.trim()) {
process.env.LMSTUDIO_API_BASE_URL = baseUrl.trim();
}
const apiKey = gcfg.get("apiKey");
if (typeof apiKey === "string") {
process.env.LMSTUDIO_API_KEY = apiKey;
}
const embeddingBaseUrl = gcfg.get("embeddingBaseUrl");
if (typeof embeddingBaseUrl === "string" && embeddingBaseUrl.trim()) {
process.env.LMSTUDIO_VISION_API_BASE_URL = embeddingBaseUrl.trim();
}
const embeddingApiKey = gcfg.get("embeddingApiKey");
if (typeof embeddingApiKey === "string") {
process.env.LMSTUDIO_VISION_API_KEY = embeddingApiKey;
}
if (typeof preview === "boolean") {
process.env.PREVIEW_IN_CHAT = preview ? "true" : "false";
}
const embedMeta = gcfg.get("embedPngMetadata");
if (typeof embedMeta === "boolean") {
process.env.EMBED_PNG_METADATA = embedMeta ? "true" : "false";
}
if (
typeof httpPort === "number" &&
Number.isFinite(httpPort) &&
httpPort > 0
) {
process.env.HTTP_SERVER_PORT = String(Math.floor(httpPort));
}
const visionPrompt = gcfg.get("visionPrompt");
if (typeof visionPrompt === "string") {
process.env.VISION_PROMPT = visionPrompt;
}
const inclMeta = gcfg.get("includeGenerationMetadata");
if (typeof inclMeta === "boolean") {
process.env.INCLUDE_GENERATION_METADATA = inclMeta ? "true" : "false";
}
const mlxMaxTokens = gcfg.get("serverMaxTokens");
if (typeof mlxMaxTokens === "number" && Number.isFinite(mlxMaxTokens) && mlxMaxTokens > 0) {
process.env.SERVER_MAX_TOKENS = String(Math.floor(mlxMaxTokens));
}
const mlxTemp = gcfg.get("serverTemperature");
if (typeof mlxTemp === "number" && Number.isFinite(mlxTemp)) {
process.env.SERVER_TEMPERATURE = String(mlxTemp);
}
const qwen3VlModelPath = gcfg.get("qwen3VlModelPath");
if (typeof qwen3VlModelPath === "string") {
process.env.LMSTUDIO_VISION_MODEL_KEY = qwen3VlModelPath;
}
const qwen3VlOdPrompt = gcfg.get("qwen3VlOdPrompt");
if (typeof qwen3VlOdPrompt === "string") {
process.env.DETECT_OD_PROMPT = qwen3VlOdPrompt;
}
const detectMaxTokens = gcfg.get("detectMaxTokens");
if (typeof detectMaxTokens === "number" && Number.isFinite(detectMaxTokens) && detectMaxTokens > 0) {
process.env.DETECT_MAX_TOKENS = String(Math.floor(detectMaxTokens));
}
const detectTemperature = gcfg.get("detectTemperature");
if (typeof detectTemperature === "number" && Number.isFinite(detectTemperature)) {
process.env.DETECT_TEMPERATURE = String(detectTemperature);
}
// Write a config snapshot once per process to avoid burst logging
try {
if (process.env.LMS_CFG_SNAPSHOT_LOGGED !== "1") {
const logsDir = getLogsDir();
if (!fs.existsSync(logsDir)) fs.mkdirSync(logsDir, { recursive: true });
const snapshot = {
PREVIEW_IN_CHAT: process.env.PREVIEW_IN_CHAT,
EMBED_PNG_METADATA: process.env.EMBED_PNG_METADATA,
HTTP_SERVER_PORT: process.env.HTTP_SERVER_PORT,
LMSTUDIO_VISION_API_BASE_URL: process.env.LMSTUDIO_VISION_API_BASE_URL,
LMSTUDIO_VISION_MODEL_KEY: process.env.LMSTUDIO_VISION_MODEL_KEY,
SERVER_MAX_TOKENS: process.env.SERVER_MAX_TOKENS,
SERVER_TEMPERATURE: process.env.SERVER_TEMPERATURE,
DETECT_MAX_TOKENS: process.env.DETECT_MAX_TOKENS,
DETECT_TEMPERATURE: process.env.DETECT_TEMPERATURE,
};
const key = JSON.stringify(snapshot);
lastConfigSnapshotKey = key;
const line = `${localTimestamp()} - Config snapshot: ${key}\n`;
fs.appendFileSync(
path.join(logsDir, "user-docs-plugin.log"),
line
);
process.env.LMS_CFG_SNAPSHOT_LOGGED = "1";
}
} catch {}
} catch {}
const tools: Tool[] = [];
tools.push(createFindDocTool(ctl));
tools.push(createReadDocTool(ctl));
tools.push(createFetchImageTool(ctl));
tools.push(createExtractImageTool(ctl));
tools.push(createReviewImageTool(ctl));
tools.push(createReviewSequenceTool(ctl));
tools.push(createAnalyseImageTool(ctl));
tools.push(createAnnotateImageTool(ctl));
tools.push(createShowImageTool(ctl));
tools.push(createSkipDocTool(ctl));
tools.push(createMemorizeDocTool(ctl));
tools.push(createRewriteDocTool(ctl));
tools.push(createForgetDocTool(ctl));
tools.push(createExportDocTool(ctl));
tools.push(createReadConfigTool(ctl));
return tools;
}