diff --git a/agent/src/index.ts b/agent/src/index.ts index 4b3f12897a..a409e0ca89 100644 --- a/agent/src/index.ts +++ b/agent/src/index.ts @@ -109,7 +109,9 @@ import net from "net"; import path from "path"; import { fileURLToPath } from "url"; import yargs from "yargs"; - +import { Plugin } from "@elizaos/core"; +import { dominosPlugin } from "@elizaos/plugin-dominos"; +import createNFTCollectionsPlugin from "@elizaos/plugin-nft-collections"; const __filename = fileURLToPath(import.meta.url); // get the resolved path to the file const __dirname = path.dirname(__filename); // get the name of the directory @@ -127,6 +129,10 @@ const logFetch = async (url: string, options: any) => { return fetch(url, options); }; +function isAllStrings(arr: unknown[]): boolean { + return Array.isArray(arr) && arr.every((item) => typeof item === "string"); +} + export function parseArguments(): { character?: string; characters?: string; @@ -307,7 +313,89 @@ export async function loadCharacters( } try { - const character: Character = await loadCharacter(resolvedPath); + const character = JSON.parse(content); + validateCharacterConfig(character); + + // .id isn't really valid + const characterId = character.id || character.name; + const characterPrefix = `CHARACTER.${characterId.toUpperCase().replace(/ /g, "_")}.`; + + const characterSettings = Object.entries(process.env) + .filter(([key]) => key.startsWith(characterPrefix)) + .reduce((settings, [key, value]) => { + const settingKey = key.slice(characterPrefix.length); + return { ...settings, [settingKey]: value }; + }, {}); + + if (Object.keys(characterSettings).length > 0) { + character.settings = character.settings || {}; + character.settings.secrets = { + ...characterSettings, + ...character.settings.secrets, + }; + } + + function isPlugin(value: any): value is Plugin { + return ( + typeof value === "object" && + value !== null && + typeof value.name === "string" && + typeof value.description === "string" && + (value.actions === undefined || + Array.isArray(value.actions)) && + (value.providers === undefined || + Array.isArray(value.providers)) && + (value.evaluators === undefined || + Array.isArray(value.evaluators)) && + (value.services === undefined || + Array.isArray(value.services)) && + (value.clients === undefined || + Array.isArray(value.clients)) + ); + } + + // Handle plugins + if (isAllStrings(character.plugins)) { + elizaLogger.info("Plugins are: ", character.plugins); + + const importedPlugins = await Promise.all( + character.plugins.map(async (plugin) => { + try { + // Dynamically import the plugin + const importedPlugin = await import(plugin); + + // Check if there's a default export + if (importedPlugin.default) { + return importedPlugin.default; + } + + // Check other exports for potential plugins + const possiblePlugins = []; + for (const [key, value] of Object.entries( + importedPlugin + )) { + // Check if the export matches the plugin type + if (isPlugin(value)) { + possiblePlugins.push(value); + } + } + + return possiblePlugins.length > 0 + ? possiblePlugins + : null; + } catch (error) { + elizaLogger.error( + `Failed to import plugin "${plugin}":`, + error + ); + return null; // Return null for failed imports + } + }) + ); + + // Flatten and filter out null or empty plugin arrays + character.plugins = importedPlugins.flat().filter(Boolean); + } loadedCharacters.push(character); elizaLogger.info( diff --git a/packages/core/src/runtime.ts b/packages/core/src/runtime.ts index 80f7987747..a6381a852b 100644 --- a/packages/core/src/runtime.ts +++ b/packages/core/src/runtime.ts @@ -402,11 +402,20 @@ export class AgentRuntime implements IAgentRuntime { this.token = opts.token; - this.plugins = [ + const combinedPlugins = [ ...(opts.character?.plugins ?? []), ...(opts.plugins ?? []), ]; + const seen = new Set(); + this.plugins = combinedPlugins.filter((plugin) => { + if (seen.has(plugin.name)) { + return false; + } + seen.add(plugin.name); + return true; + }); + this.plugins.forEach((plugin) => { plugin.actions?.forEach((action) => { this.registerAction(action);