Lumi/plugins/lumi_ai_web_search/index.js
2026-06-13 21:32:36 +02:00

65 lines
2.7 KiB
JavaScript

const net = require("net");
const { WebSearchTool } = require("./backend/search_tool");
const { normalizeOrigin } = require("./backend/result_formatter");
const { readSettings } = require("./backend/settings");
const { isLocalHostname, isPrivateAddress } = require("./backend/url_policy");
module.exports.checkAvailability = ({ paths }) => {
const settings = readSettings(paths.data);
if (!settings.enabled) {
return { available: false, message: "Web search is disabled in tool settings." };
}
if (!settings.provider_endpoint) {
return { available: false, message: "Configure a search provider endpoint in Tool Settings." };
}
try {
const endpoint = new URL(settings.provider_endpoint);
const hostname = endpoint.hostname.replace(/^\[|\]$/g, "");
if (!["http:", "https:"].includes(endpoint.protocol) || endpoint.username || endpoint.password ||
isLocalHostname(hostname) || (net.isIP(hostname) && isPrivateAddress(hostname))) {
return { available: false, message: "Search provider endpoint is blocked by network safety rules." };
}
} catch {
return { available: false, message: "Search provider endpoint is invalid." };
}
return { available: true };
};
module.exports.register = ({ registerTool, paths }) => {
const search = new WebSearchTool({ dataDir: paths.data });
registerTool({
tool_id: "lumi_ai_web_search.search",
display_name: "Search the web",
description: "Search current public web information only when verified local Lumi context is insufficient or current external information is explicitly needed. Returns normalized, policy-filtered results for final answer formatting.",
required_role: "user",
required_permission: "lumi_ai_web_search.search",
audit_category: "web_search",
confirmation_required: false,
risk_level: "low",
schema: {
query: { type: "string", required: true },
reason: {
type: "string",
required: true,
enum: [
"fact_lookup",
"resource_lookup",
"troubleshooting",
"documentation_lookup",
"news_or_recent",
"general_lookup"
]
},
requested_depth: { type: "string", required: false, enum: ["search", "full_page"] },
freshness: { type: "string", required: false }
},
permission_check: ({ user, context }) => {
const settings = readSettings(paths.data);
const origin = normalizeOrigin(context?.origin || context?.platform || "other");
return Boolean(user?.id) && settings.enabled && settings.allowed_origins.includes(origin);
},
workflow_handler: ({ arguments: args, user, ctx }) =>
search.run({ ...args, user, ctx })
});
};