"use strict";
/**
* @file webTools.ts
* Web tools: search, fetch content, Wikipedia.
* Search logic lives in webSearch.ts (shared with secondary agent).
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.createWebTools = createWebTools;
const sdk_1 = require("@lmstudio/sdk");
const zod_1 = require("zod");
const shared_1 = require("./shared");
const webSearch_1 = require("./webSearch");
const errorCodes_1 = require("./errorCodes");
const spillToDisk_1 = require("./spillToDisk");
function createWebTools(config, limits) {
const tools = [];
const searchDescription = config.searxngUrl
? "Search the web using SearXNG."
: "Search the web using DuckDuckGo.";
tools.push((0, sdk_1.tool)({
name: "web_search",
description: searchDescription,
parameters: { query: zod_1.z.string() },
implementation: async ({ query }) => {
// Strategy 1: SearXNG (if configured)
if (config.searxngUrl) {
const results = await (0, webSearch_1.searchSearXNG)(config.searxngUrl, query);
if (results)
return { results, total_found: results.length, source: "searxng" };
// SearXNG failed — fall through to DDG
}
// Strategy 2: DDG Lite
const results = await (0, webSearch_1.searchDDGLite)(query);
if (results)
return { results, total_found: results.length, source: "duckduckgo" };
return { ...(0, errorCodes_1.toolError)(errorCodes_1.SERVICE_UNAVAILABLE, "Search temporarily unavailable. Try again in a few minutes."), results: [] };
},
}));
tools.push((0, sdk_1.tool)({
name: "fetch_web_content",
description: "Fetch the clean, text-based content of a webpage URL.",
parameters: { url: zod_1.z.string() },
implementation: async ({ url }) => {
try {
const response = await fetch(url);
if (!response.ok)
throw new Error(`HTTP error! status: ${response.status}`);
let text = await response.text();
const result = { url, status: response.status };
const titleMatch = text.match(/<title[^>]*>([^<]+)<\/title>/i);
if (titleMatch)
result.title = titleMatch[1];
let previousLength;
do {
previousLength = text.length;
text = text.replace(/<script\b[^>]*>[\s\S]*?<\/script>/gi, "");
text = text.replace(/<style\b[^>]*>[\s\S]*?<\/style>/gi, "");
} while (text.length < previousLength);
text = text.replace(/<nav\b[^>]*>[\s\S]*?<\/nav>/gi, "").replace(/<footer\b[^>]*>[\s\S]*?<\/footer>/gi, "").replace(/<header\b[^>]*>[\s\S]*?<\/header>/gi, "").replace(/<aside\b[^>]*>[\s\S]*?<\/aside>/gi, "");
text = text.replace(/<\/div>/gi, "\n").replace(/<\/p>/gi, "\n").replace(/<br\s*\/?>/gi, "\n");
do {
previousLength = text.length;
text = text.replace(/<[^>]+>/g, "");
} while (text.length < previousLength);
text = text.replace(/</g, "<").replace(/>/g, ">").replace(/"/g, '"').replace(/ /g, " ").replace(/&/g, "&");
text = text.replace(/[ \t]+/g, ' ').replace(/\n\s*\n/g, "\n\n").trim();
const MAX = limits?.maxWebContent ?? 6_000;
const spill = await (0, spillToDisk_1.spillIfNeeded)(text, MAX, "web");
result.content = spill.preview;
if (spill.spilled)
result.content_full = spill.spillPath;
return result;
}
catch (error) {
return (0, errorCodes_1.toolError)(errorCodes_1.HTTP_ERROR, `Failed to fetch URL: ${error instanceof Error ? error.message : String(error)}`);
}
},
}));
tools.push((0, sdk_1.tool)({
name: "wikipedia_search",
description: "Search Wikipedia for a given query and return page summaries.",
parameters: {
query: zod_1.z.string(),
lang: zod_1.z.string().optional().describe("Language code (default: en)"),
},
implementation: (0, shared_1.createSafeToolImplementation)(async ({ query, lang = "en" }) => {
try {
const searchUrl = `https://${lang}.wikipedia.org/w/api.php?action=query&list=search&srsearch=${encodeURIComponent(query)}&format=json`;
const searchData = await (await fetch(searchUrl)).json();
if (!searchData.query?.search?.length)
return { results: "No Wikipedia articles found." };
// Fetch pages in parallel instead of serial
const pages = await Promise.all(searchData.query.search.slice(0, 3).map(async (item) => {
const pageUrl = `https://${lang}.wikipedia.org/w/api.php?action=query&prop=extracts&exintro&explaintext&pageids=${item.pageid}&format=json`;
const pageData = await (await fetch(pageUrl)).json();
const page = pageData.query.pages[item.pageid];
return {
title: item.title,
summary: page.extract.substring(0, limits?.maxWikiSummary ?? 2_000) + (page.extract.length > (limits?.maxWikiSummary ?? 2_000) ? "..." : ""),
url: `https://${lang}.wikipedia.org/wiki/${encodeURIComponent(item.title.replace(/ /g, "_"))}`,
};
}));
return { results: pages };
}
catch (error) {
return (0, errorCodes_1.toolError)(errorCodes_1.SERVICE_UNAVAILABLE, `Wikipedia search failed: ${error instanceof Error ? error.message : String(error)}`);
}
}, config.enableWikipedia, "wikipedia_search"),
}));
return tools;
}
//# sourceMappingURL=webTools.js.map