src / toolsProvider.ts
import { tool, Tool, ToolsProviderController } from "@lmstudio/sdk";
import { z } from "zod";
import { configSchematics } from "./config";
export async function toolsProvider(ctl: ToolsProviderController) {
const tools: Tool[] = [];
const searchTool = tool({
name: "searxng_search",
description:
"Search the web using SearXNG meta-search engine. Returns title, URL, content snippet, and source engine for each result.",
parameters: {
query: z.string().min(1).describe("The search query"),
},
implementation: async ({ query }, { status, signal }) => {
const baseUrl = ctl.getPluginConfig(configSchematics).get("searxngUrl") as string;
const language = ctl.getPluginConfig(configSchematics).get("language") as string;
const maxResults = ctl.getPluginConfig(configSchematics).get("maxResults") as number;
const url = `${baseUrl}/search?format=json&q=${encodeURIComponent(query)}&language=${language}&categories=general`;
status(`Searching SearXNG for "${query}"...`);
const response = await fetch(url, {
method: "GET",
signal,
});
if (!response.ok) {
return `Error: Failed to search SearXNG. Status: ${response.status} ${response.statusText}`;
}
const data = await response.json() as {
query: string;
results: Array<{
title: string;
url: string;
content: string;
engine: string;
score: number;
publishedDate?: string;
}>;
unresponsive_engines?: Array<{ engine: string; error: string }>;
};
const results = data.results.slice(0, maxResults);
if (results.length === 0) {
const engines = data.unresponsive_engines;
if (engines && engines.length > 0) {
const failed = engines.map((e) => `${e.engine}(${e.error})`).join(", ");
return `No results found. Unresponsive engines: ${failed}`;
}
return `No results found for "${query}".`;
}
const formatted = results
.map(
(r, i) =>
`${i + 1}. ${r.title}\n URL: ${r.url}\n Source: ${r.engine}\n Snippet: ${r.content}`,
)
.join("\n\n");
return `Found ${results.length} result(s):\n\n${formatted}`;
},
});
tools.push(searchTool);
return tools;
}