UNPKG

create-eliza

Version:

Initialize an Eliza project

633 lines (624 loc) 22.8 kB
import { createRequire } from 'module'; const require = createRequire(import.meta.url); import { loadProject } from "./chunk-HOYDBWZH.js"; import { AgentServer, jsonToCharacter, loadCharacterTryPath, promptForEnvVars } from "./chunk-4XN5G4Z4.js"; import { require_main } from "./chunk-ZMJ3QLUC.js"; import { AgentRuntime, logger, settings, stringToUuid } from "./chunk-DWYHKJQZ.js"; import { Command } from "./chunk-S757QXWN.js"; import { __toESM } from "./chunk-WCMDOJQK.js"; // src/commands/test.ts import * as fs from "node:fs"; import { existsSync as existsSync2, readFileSync } from "node:fs"; import * as net from "node:net"; import * as os from "node:os"; import path from "node:path"; var dotenv = __toESM(require_main(), 1); // src/testRunner.ts var TestRunner = class { runtime; projectAgent; stats; isDirectPluginTest; constructor(runtime, projectAgent) { this.runtime = runtime; this.projectAgent = projectAgent; this.stats = { total: 0, passed: 0, failed: 0, skipped: 0 }; if (projectAgent?.plugins?.length === 1 && (projectAgent.character.name.includes(`Test Agent for ${projectAgent.plugins[0].name}`) || projectAgent.character.name.toLowerCase().includes("test") && projectAgent.character.name.toLowerCase().includes(projectAgent.plugins[0].name.toLowerCase())) || // Alternatively, if we were launched from within a plugin directory, consider it a direct test process.env.ELIZA_TESTING_PLUGIN === "true") { this.isDirectPluginTest = true; logger.debug("This is a direct plugin test - will only run tests for this plugin"); } else { this.isDirectPluginTest = false; } } /** * Runs a test suite * @param suite The test suite to run */ async runTestSuite(suite) { logger.info(` Running test suite: ${suite.name}`); for (const test2 of suite.tests) { this.stats.total++; try { logger.info(` Running test: ${test2.name}`); await test2.fn(this.runtime); this.stats.passed++; logger.success(` \u2713 ${test2.name}`); } catch (error) { this.stats.failed++; logger.error(` \u2717 ${test2.name}`); logger.error(` ${error.message}`); } } } /** * Runs project agent tests */ async runProjectTests(options) { if (!this.projectAgent?.tests || options.skipProjectTests || this.isDirectPluginTest) { if (this.isDirectPluginTest) { logger.info("Skipping project tests when directly testing a plugin"); } return; } logger.info("\nRunning project tests..."); const testSuites = Array.isArray(this.projectAgent.tests) ? this.projectAgent.tests : [this.projectAgent.tests]; for (const suite of testSuites) { if (!suite) { continue; } if (options.filter && !suite.name.includes(options.filter)) { this.stats.skipped++; continue; } await this.runTestSuite(suite); } } /** * Runs plugin tests */ async runPluginTests(options) { if (options.skipPlugins && !this.isDirectPluginTest) { return; } if (this.isDirectPluginTest) { logger.info("\nRunning plugin tests..."); const plugin = this.projectAgent?.plugins?.[0]; if (!plugin || !plugin.tests) { logger.warn(`No tests found for this plugin (${plugin?.name || "unknown plugin"})`); logger.info("To add tests to your plugin, include a 'tests' property with an array of test suites."); logger.info("Example:"); logger.info(` export const myPlugin = { name: "my-plugin", description: "My awesome plugin", // ... other plugin properties ... tests: [ { name: "Basic Tests", tests: [ { name: "should do something", fn: async (runtime) => { // Test code here } } ] } ] }; `); return; } logger.info(`Found test suites for plugin: ${plugin.name}`); const testSuites = Array.isArray(plugin.tests) ? plugin.tests : [plugin.tests]; for (const suite of testSuites) { if (!suite) { continue; } if (options.filter && !suite.name.includes(options.filter)) { logger.info(`Skipping test suite "${suite.name}" because it doesn't match filter "${options.filter}"`); this.stats.skipped++; continue; } await this.runTestSuite(suite); } } else { logger.info("\nRunning project plugin tests..."); const plugins = this.projectAgent?.plugins || []; if (plugins.length === 0) { logger.info("No plugins defined directly in project to test"); return; } logger.info(`Found ${plugins.length} plugins defined in project`); for (const plugin of plugins) { try { if (!plugin) { continue; } logger.info(`Testing plugin: ${plugin.name || "unnamed plugin"}`); const pluginTests = plugin.tests; if (!pluginTests) { logger.info(`No tests found for plugin: ${plugin.name || "unnamed plugin"}`); continue; } const testSuites = Array.isArray(pluginTests) ? pluginTests : [pluginTests]; for (const suite of testSuites) { if (!suite) { continue; } if (options.filter && !suite.name.includes(options.filter)) { logger.info(`Skipping test suite "${suite.name}" (doesn't match filter)`); this.stats.skipped++; continue; } await this.runTestSuite(suite); } } catch (error) { logger.error(`Error running tests for plugin ${plugin.name}:`, error); this.stats.failed++; } } } } /** * Runs all tests in the project * @param options Test options */ async runTests(options = {}) { await this.runProjectTests(options); await this.runPluginTests(options); logger.info(` Test Summary: ${this.stats.passed} passed, ${this.stats.failed} failed, ${this.stats.skipped} skipped`); return this.stats; } }; // src/commands/test.ts async function checkPortAvailable(port) { return new Promise((resolve) => { const server = net.createServer(); server.once("error", () => { resolve(false); }); server.once("listening", () => { server.close(); resolve(true); }); server.listen(port); }); } async function startAgent(character, server, init, plugins = []) { character.id ??= stringToUuid(character.name); try { if (server.database) { try { logger.debug(`Creating agent directly in database: ${character.name}`); await server.database.createAgent({ id: character.id, name: `${character.name} (DB)`, // Use template literal instead of concatenation bio: character.bio || "", system: character.system || "", enabled: true, createdAt: Date.now(), updatedAt: Date.now() }); logger.debug(`Created agent in database: ${character.id}`); } catch (error) { logger.debug(`Note: Agent creation might have failed (possibly already exists): ${error instanceof Error ? error.message : String(error)}`); } try { const agent = await server.database.getAgent(character.id); if (agent) { logger.debug(`Verified agent exists in database with ID: ${character.id}`); } else { logger.warn(`Warning: Agent does not exist in database after creation attempt: ${character.id}`); } } catch (verifyError) { logger.warn(`Warning: Error verifying agent: ${verifyError instanceof Error ? verifyError.message : String(verifyError)}`); } } const validPlugins = plugins.filter((plugin) => plugin != null); if (validPlugins.length > 0) { logger.debug(`Starting agent with ${validPlugins.length} plugins:`); validPlugins.forEach((plugin, i) => { logger.debug(` Plugin ${i + 1}: ${plugin.name || "unnamed"}`); logger.debug(` Plugin properties: ${Object.keys(plugin).join(", ")}`); logger.debug(` Plugin init type: ${typeof plugin.init}`); }); } else if (plugins.length > 0) { logger.warn(`Found ${plugins.length} plugins but none were valid objects`); } const runtime = new AgentRuntime({ character, plugins: validPlugins }); const originalInitialize = runtime.initialize.bind(runtime); runtime.initialize = async () => { try { return await originalInitialize(); } catch (err) { if (err instanceof Error && (err.message.includes("foreign key constraint") || err.message.includes("does not exist in database"))) { logger.warn(`Note: Ignoring common initialization error: ${err.message}`); return; } throw err; } }; if (init) { try { await init(runtime); } catch (initError) { logger.error(`Error in agent init function for ${character.name}:`, initError); throw initError; } } try { await runtime.initialize(); } catch (initError) { logger.error(`Error initializing runtime for ${character.name}:`, initError); if (validPlugins.length > 0) { logger.debug(`Agent has ${validPlugins.length} plugins:`); validPlugins.forEach((plugin, index) => { logger.debug(` Plugin ${index + 1}: ${plugin.name || "unnamed"}`); if (plugin.init && typeof plugin.init !== "function") { logger.error(` Plugin ${plugin.name} has an init property that is not a function`); } }); } throw initError; } server.registerAgent(runtime); logger.debug(`Started ${runtime.character.name} as ${runtime.agentId}`); return runtime; } catch (error) { logger.error(`Failed to start agent ${character.name}:`, error); throw error; } } var runAgentTests = async (options) => { try { const runtimes = []; const projectAgents = []; const homeDir = os.homedir(); const elizaDir = path.join(homeDir, ".eliza"); const elizaDbDir = path.join(elizaDir, "db"); const envFilePath = path.join(elizaDir, ".env"); logger.info("Setting up environment..."); logger.info(`Home directory: ${homeDir}`); logger.info(`Eliza directory: ${elizaDir}`); logger.info(`Database directory: ${elizaDbDir}`); logger.info(`Environment file: ${envFilePath}`); if (!fs.existsSync(elizaDir)) { logger.info(`Creating directory: ${elizaDir}`); fs.mkdirSync(elizaDir, { recursive: true }); logger.info(`Created directory: ${elizaDir}`); } if (!fs.existsSync(elizaDbDir)) { logger.info(`Creating database directory: ${elizaDbDir}`); fs.mkdirSync(elizaDbDir, { recursive: true }); logger.info(`Created database directory: ${elizaDbDir}`); } process.env.PGLITE_DATA_DIR = elizaDbDir; logger.info(`Using database directory: ${elizaDbDir}`); if (fs.existsSync(envFilePath)) { logger.info(`Loading environment variables from: ${envFilePath}`); dotenv.config({ path: envFilePath }); logger.info("Environment variables loaded"); } else { logger.warn(`Environment file not found: ${envFilePath}`); } try { logger.info("Configuring database..."); await promptForEnvVars("pglite"); logger.info("Database configuration completed"); } catch (error) { logger.error("Error configuring database:", error); if (error instanceof Error) { logger.error("Error details:", error.message); logger.error("Stack trace:", error.stack); } throw error; } const postgresUrl = process.env.POSTGRES_URL; logger.info(`PostgreSQL URL: ${postgresUrl ? "found" : "not found"}`); logger.info("Creating server instance..."); const server = new AgentServer({ dataDir: elizaDbDir, postgresUrl }); logger.info("Server instance created"); logger.info("Waiting for database initialization..."); try { await new Promise((resolve, reject) => { let initializationAttempts = 0; const maxAttempts = 5; const checkInterval = setInterval(async () => { try { if (server.database?.isInitialized) { clearInterval(checkInterval); resolve(); return; } initializationAttempts++; try { await server.database?.init(); clearInterval(checkInterval); resolve(); } catch (initError) { logger.warn(`Database initialization attempt ${initializationAttempts}/${maxAttempts} failed:`, initError); if (initializationAttempts >= maxAttempts) { if (server.database?.connection) { logger.warn("Max initialization attempts reached, but database connection exists. Proceeding anyway."); clearInterval(checkInterval); resolve(); } else { clearInterval(checkInterval); reject(new Error(`Database initialization failed after ${maxAttempts} attempts`)); } } } } catch (error) { logger.error("Error during database initialization check:", error); if (error instanceof Error) { logger.error("Error details:", error.message); logger.error("Stack trace:", error.stack); } clearInterval(checkInterval); reject(error); } }, 1e3); setTimeout(() => { clearInterval(checkInterval); if (server.database?.connection) { logger.warn("Database initialization timeout, but connection exists. Proceeding anyway."); resolve(); } else { reject(new Error("Database initialization timed out after 30 seconds")); } }, 3e4); }); logger.info("Database initialized successfully"); } catch (error) { logger.error("Failed to initialize database:", error); if (error instanceof Error) { logger.error("Error details:", error.message); logger.error("Stack trace:", error.stack); } throw error; } logger.info("Setting up server properties..."); server.startAgent = async (character) => { logger.info(`Starting agent for character ${character.name}`); return startAgent(character, server); }; server.loadCharacterTryPath = loadCharacterTryPath; server.jsonToCharacter = jsonToCharacter; logger.info("Server properties set up"); let serverPort = options.port || Number.parseInt(settings.SERVER_PORT || "3000"); const isLikelyPluginDir = checkIfLikelyPluginDir(process.cwd()); logger.info(`Loading ${isLikelyPluginDir ? "plugin" : "project"} from current directory: ${process.cwd()}`); if (isLikelyPluginDir) { process.env.ELIZA_TESTING_PLUGIN = "true"; logger.debug("Set ELIZA_TESTING_PLUGIN=true for direct plugin testing"); } let project; try { logger.debug("Attempting to load module..."); project = await loadProject(process.cwd()); if (project.isPlugin) { logger.info(`Plugin loaded successfully: ${project.pluginModule?.name}`); logger.info(`Description: ${project.pluginModule?.description}`); if (project.pluginModule?.tests) { const testSuites = Array.isArray(project.pluginModule.tests) ? project.pluginModule.tests : [project.pluginModule.tests]; logger.info(`Plugin has ${testSuites.length} test suites:`); testSuites.forEach((suite, i) => { if (suite) { logger.info(` Suite ${i + 1}: ${suite.name} (${suite.tests?.length || 0} tests)`); } }); } else { logger.info("Plugin does not have any test suites defined"); } logger.info("Created a test agent to run plugin tests"); } else { logger.info("Project loaded successfully:", JSON.stringify(project, null, 2)); } if (!project || !project.agents || project.agents.length === 0) { throw new Error("No agents found in project configuration"); } logger.info(`Found ${project.agents.length} agents in ${project.isPlugin ? "plugin" : "project"} configuration`); } catch (error) { if (isLikelyPluginDir) { logger.error("Error loading plugin for testing:", error); if (error instanceof Error && error.message.includes("No agents found in project")) { logger.error("This appears to be a plugin directory, but we couldn't load the plugin properly."); logger.error("Make sure your plugin exports a valid plugin object with name and description properties."); logger.error("Example plugin structure:"); logger.error(` export const myPlugin = { name: "my-plugin", description: "Description of my plugin", // other plugin properties }; export default myPlugin; `); } } else { logger.error("Error loading project:", error); } if (error instanceof Error) { logger.error("Error details:", error.message); logger.error("Stack trace:", error.stack); } throw error; } logger.info("Checking port availability..."); while (!await checkPortAvailable(serverPort)) { logger.warn(`Port ${serverPort} is in use, trying ${serverPort + 1}`); serverPort++; } logger.info(`Using port ${serverPort}`); logger.info("Starting server..."); try { await server.start(serverPort); logger.info("Server started successfully"); } catch (error) { logger.error("Error starting server:", error); if (error instanceof Error) { logger.error("Error details:", error.message); logger.error("Stack trace:", error.stack); } throw error; } try { logger.info(`Found ${project.agents.length} agents in ${project.isPlugin ? "plugin" : "project"}`); for (const agent of project.agents) { try { logger.info(`Starting agent: ${agent.character.name}`); const runtime = await startAgent( agent.character, server, agent.init, agent.plugins || [] ); runtimes.push(runtime); projectAgents.push(agent); await new Promise((resolve) => setTimeout(resolve, 1e3)); } catch (agentError) { logger.error( `Error starting agent ${agent.character.name}:`, agentError ); if (agentError instanceof Error) { logger.error("Error details:", agentError.message); logger.error("Stack trace:", agentError.stack); } throw agentError; } } if (runtimes.length === 0) { throw new Error("Failed to start any agents from project"); } logger.info(`Successfully started ${runtimes.length} agents from ${project.isPlugin ? "plugin" : "project"}`); let totalFailed = 0; for (let i = 0; i < runtimes.length; i++) { const runtime = runtimes[i]; const projectAgent = projectAgents[i]; if (project.isPlugin) { logger.info(`Running tests for plugin: ${project.pluginModule?.name}`); } else { logger.info(`Running tests for agent: ${runtime.character.name}`); } const testRunner = new TestRunner(runtime, projectAgent); const skipPlugins = project.isPlugin ? true : options.skipPlugins; const results = await testRunner.runTests({ filter: options.plugin, skipPlugins, skipProjectTests: options.skipProjectTests }); totalFailed += results.failed; } await server.stop(); process.exit(totalFailed > 0 ? 1 : 0); } catch (error) { logger.error("Error running tests:", error); if (error instanceof Error) { logger.error("Error details:", error.message); logger.error("Stack trace:", error.stack); } await server.stop(); throw error; } } catch (error) { logger.error("Error in runAgentTests:", error); if (error instanceof Error) { logger.error("Error details:", error.message); logger.error("Stack trace:", error.stack); } else { logger.error("Unknown error type:", typeof error); logger.error("Error value:", error); try { logger.error("Stringified error:", JSON.stringify(error, null, 2)); } catch (e) { logger.error("Could not stringify error:", e); } } throw error; } }; function checkIfLikelyPluginDir(dir) { const packageJsonPath = path.join(dir, "package.json"); if (existsSync2(packageJsonPath)) { try { const packageJson = JSON.parse(readFileSync(packageJsonPath, "utf-8")); if (packageJson.name?.includes("plugin")) { return true; } if (packageJson.keywords?.some((k) => k.includes("elizaos") || k.includes("eliza") || k.includes("plugin"))) { return true; } } catch (e) { } } return existsSync2(path.join(dir, "src/plugin.ts")) || existsSync2(path.join(dir, "src/index.ts")) && !existsSync2(path.join(dir, "src/agent.ts")) || dir.includes("plugin"); } var test = new Command().name("test").description("Run tests for Eliza agent plugins").option( "-p, --port <port>", "Port to listen on", (val) => Number.parseInt(val) ).option("-P, --plugin <name>", "Name of plugin to test").option("--skip-plugins", "Skip plugin tests").option("--skip-project-tests", "Skip project tests").action(async (options) => { logger.info("Starting test command..."); logger.info("Command options:", options); try { logger.info("Running agent tests..."); await runAgentTests(options); } catch (error) { logger.error("Error running tests:", error); if (error instanceof Error) { logger.error("Error details:", error.message); logger.error("Stack trace:", error.stack); } else { logger.error("Unknown error type:", typeof error); logger.error("Error value:", error); try { logger.error("Stringified error:", JSON.stringify(error, null, 2)); } catch (e) { logger.error("Could not stringify error:", e); } } process.exit(1); } }); function registerCommand(cli) { return cli.addCommand(test); } export { test, registerCommand }; //# sourceMappingURL=chunk-ZRBOK25A.js.map