Lumi/plugins/lumi_ai/tests/tool-loading.js
2026-06-14 04:18:36 +02:00

322 lines
10 KiB
JavaScript

const assert = require("assert");
const fs = require("fs");
const os = require("os");
const path = require("path");
const { AiProvider } = require("../backend/ai_provider");
const { buildPrompt } = require("../backend/prompt_builder");
const { ToolInstaller } = require("../backend/tool_installer");
const { ToolLoader } = require("../backend/tool_loader");
const { ToolManager } = require("../backend/tool_manager");
const { ToolRegistry, parseToolCallResult } = require("../backend/tool_router");
async function run() {
const root = fs.mkdtempSync(path.join(os.tmpdir(), "lumi-ai-loading-"));
try {
const pluginsDir = path.join(root, "plugins");
const stateFile = path.join(root, "data", "tools", "enabled.json");
fs.mkdirSync(pluginsDir, { recursive: true });
fs.mkdirSync(path.join(pluginsDir, "lumi_ai"), { recursive: true });
createTool(pluginsDir, "lumi_ai_web_search", { readOnly: true });
createTool(pluginsDir, "lumi_ai_test_tool", { readOnly: true });
createInvalidTool(pluginsDir, "lumi_ai_invalid");
createMissingEntrypointTool(pluginsDir, "lumi_ai_missing_entrypoint");
const audit = [];
const registry = new ToolRegistry((entry) => audit.push(entry));
const installer = new ToolInstaller({
pluginsDir,
stagingRoot: path.join(root, "staging"),
repoClient: {}
});
const loader = new ToolLoader({
registry,
installer,
settings: { getSetting: (_key, fallback) => fallback },
stateFile,
lumiAiVersion: "0.8.0",
lumiVersion: "0.1.0"
});
const manager = new ToolManager({
loader,
installer,
settings: { describe: () => ({}) },
repoClient: {
async discover() {
return {
repository: "local",
branch: "main",
checked_at: new Date().toISOString(),
cached: false,
stale: false,
tools: []
};
}
}
});
assert.deepEqual(
installer.scanLocal().map((entry) => entry.tool_id).sort(),
["lumi_ai_invalid", "lumi_ai_missing_entrypoint", "lumi_ai_test_tool", "lumi_ai_web_search"]
);
loader.setEnabled("lumi_ai_web_search", true);
loader.setEnabled("lumi_ai_test_tool", true);
loader.setEnabled("lumi_ai_invalid", true);
loader.setEnabled("lumi_ai_missing_entrypoint", true);
await loader.loadEnabled();
assert.equal(registry.has("lumi_ai_web_search.lookup"), true);
assert.equal(registry.has("lumi_ai_test_tool.lookup"), true);
assert.equal(loader.status("lumi_ai_invalid").state, "unavailable");
assert.equal(loader.status("lumi_ai_missing_entrypoint").state, "unavailable");
await loader.disable("lumi_ai_test_tool");
assert.equal(registry.has("lumi_ai_test_tool.lookup"), false);
const disabledDiagnostics = await manager.diagnostics({
role: "user",
user: actor("user"),
context: origin("webui")
});
assert.equal(
disabledDiagnostics.plugins.find((plugin) => plugin.tool_id === "lumi_ai_test_tool").hidden_reason,
"disabled"
);
assert.equal(
disabledDiagnostics.plugins.find((plugin) => plugin.tool_id === "lumi_ai_invalid").hidden_reason,
"schema_invalid"
);
const webui = registry.inspect({
role: "user",
user: actor("user"),
context: origin("webui")
});
assert(webui.exposed.some((tool) => tool.tool_id === "lumi_ai_web_search.lookup"));
const discord = registry.inspect({
role: "user",
user: actor("user"),
context: origin("discord", false)
});
assert(discord.exposed.some((tool) => tool.tool_id === "lumi_ai_web_search.lookup"));
registry.register(actionDefinition());
const platformActions = registry.inspect({
role: "admin",
user: actor("admin"),
context: origin("discord", false)
});
assert.equal(
platformActions.considered.find((entry) => entry.tool.tool_id === "test_admin.remove").reason,
"scope_blocked"
);
const prompt = buildPrompt({
config: promptConfig(),
role: "user",
message: "Search for current Lumi information",
tools: webui.exposed,
originContext: origin("webui")
});
assert(prompt.includes('{"type":"tool_call","tool":"tool_id","arguments":{}}'));
assert(prompt.includes('"tool_id":"lumi_ai_web_search.lookup"'));
assert(prompt.includes('"kind":"lookup/read"'));
assert.equal(parseToolCallResult('{"type":"tool_call","tool":"x","arguments":{}}').status, "valid");
assert.equal(parseToolCallResult('Use this: {"type":"tool_call","tool":"x","arguments":{}}').status, "malformed");
assert.equal(
parseToolCallResult('{"type":"tool_call","tool":"x","arguments":{},"comment":"no"}').status,
"malformed"
);
await verifyReadOnlyFinalization(registry);
verifyConfirmation(registry);
assert(audit.some((entry) => entry.kind === "tool" && entry.status === "success"));
} finally {
fs.rmSync(root, { recursive: true, force: true });
}
console.log("Lumi AI tool loading verification passed.");
}
async function verifyReadOnlyFinalization(registry) {
const outputs = [
'{"type":"tool_call","tool":"lumi_ai_web_search.lookup","arguments":{"query":"current Lumi release"}}',
"The current result is Lumi 1.2.3 according to the returned source."
];
let inferCalls = 0;
const provider = new AiProvider({
getConfig: () => ({
selected_model_id: "test",
support_scope: {},
instructions: {},
logging: {},
hard_generation_timeout_ms: 5000,
output_budgets: { simple_answer: 128 }
}),
runtime: {
infer: async () => ({
choices: [{ message: { content: outputs[inferCalls++] }, finish_reason: "stop" }],
usage: { prompt_tokens: 10, completion_tokens: 5 }
})
},
queue: {
length: 0,
run: async (_userId, _role, callback) => callback(0)
},
tools: registry,
metrics: { record() {} },
getContext: () => [],
lookupRepo: () => null,
getRepoContext: () => [],
getCorrections: () => []
});
const result = await provider.generate({
message: "What is the current Lumi release?",
user: actor("user"),
sessionId: "tool-test",
originContext: origin("webui"),
allowTools: true
});
assert.equal(inferCalls, 2);
assert.match(result.text, /Lumi 1\.2\.3/);
assert.deepEqual(result.tool_result, {
status: "ok",
query: "current Lumi release",
results: [{ title: "Lumi 1.2.3", url: "https://example.test/lumi" }]
});
}
function verifyConfirmation(registry) {
const prepared = registry.prepare({
tool: "test_admin.remove",
args: { target: "example" },
user: actor("admin"),
role: "admin",
sessionId: "confirm-test",
context: origin("webui")
});
assert.equal(prepared.execute, false);
assert(prepared.confirmation.id);
}
function createTool(pluginsDir, toolId, { readOnly }) {
const directory = path.join(pluginsDir, toolId);
fs.mkdirSync(directory, { recursive: true });
const metadata = {
tool_id: toolId,
display_name: toolId,
version: "1.0.0",
description: "Test lookup tool",
scope: "assistant",
permissions: { required_role: "user" },
capabilities: ["Current information lookup"],
limitations: ["Test data only"],
tool_type: "lookup",
entrypoints: { backend: "index.js" },
risk_level: "low",
confirmation_required: false
};
fs.writeFileSync(path.join(directory, "tool_info.json"), `${JSON.stringify(metadata, null, 2)}\n`);
fs.writeFileSync(path.join(directory, "readme.md"), `# ${toolId}\n`);
fs.writeFileSync(path.join(directory, "index.js"), `
module.exports.register = ({ registerTool }) => registerTool({
tool_id: "${toolId}.lookup",
display_name: "Lookup",
description: "Looks up current test information.",
required_role: "user",
required_permission: "${toolId}.use",
audit_category: "lookup",
confirmation_required: false,
risk_level: "low",
read_only: ${readOnly},
use_cases: ["Current information"],
output_expectations: "Structured test results.",
schema: { query: { type: "string", required: true } },
permission_check: ({ user }) => Boolean(user && user.id),
workflow_handler: async ({ arguments: args }) => ({
status: "ok",
query: args.query,
results: [{ title: "Lumi 1.2.3", url: "https://example.test/lumi" }]
})
});
`);
}
function createInvalidTool(pluginsDir, toolId) {
const directory = path.join(pluginsDir, toolId);
fs.mkdirSync(directory, { recursive: true });
fs.writeFileSync(path.join(directory, "tool_info.json"), "{invalid");
fs.writeFileSync(path.join(directory, "readme.md"), "# Invalid\n");
}
function createMissingEntrypointTool(pluginsDir, toolId) {
const directory = path.join(pluginsDir, toolId);
fs.mkdirSync(directory, { recursive: true });
const metadata = {
tool_id: toolId,
display_name: toolId,
version: "1.0.0",
description: "Missing entrypoint test",
scope: "assistant",
permissions: { required_role: "user" },
capabilities: ["Test"],
limitations: ["Unavailable"],
entrypoints: { backend: "missing.js" }
};
fs.writeFileSync(path.join(directory, "tool_info.json"), `${JSON.stringify(metadata, null, 2)}\n`);
fs.writeFileSync(path.join(directory, "readme.md"), "# Missing entrypoint\n");
}
function actionDefinition() {
return {
tool_id: "test_admin.remove",
display_name: "Remove test data",
description: "Mutates test data.",
owning_plugin: "test_admin",
required_role: "admin",
required_permission: "test.remove",
audit_category: "test",
confirmation_required: true,
risk_level: "sensitive",
read_only: false,
schema: { target: { type: "string", required: true } },
permission_check: ({ user }) => user?.isAdmin === true,
workflow_handler: async () => ({ status: "ok" })
};
}
function actor(role) {
return {
id: `${role}-1`,
username: role,
isAdmin: role === "admin",
isMod: role === "mod"
};
}
function origin(platform, webuiActionsAllowed = platform === "webui") {
return {
origin: platform,
platform,
max_message_length: platform === "twitch" ? 450 : 1900,
permission_context: {
identified_user: true,
webui_actions_allowed: webuiActionsAllowed
}
};
}
function promptConfig() {
return {
support_scope: {},
instructions: {
roleplay_intensity: 0,
community_tone: "",
admin_custom: ""
}
};
}
run().catch((error) => {
console.error(error);
process.exitCode = 1;
});