Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix: character file plugins import error #2025

Closed
wants to merge 13 commits into from
95 changes: 94 additions & 1 deletion agent/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,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";
import { emailPlugin } from "@elizaos/plugin-email";
import { emailAutomationPlugin } from "@elizaos/plugin-email-automation";
import { seiPlugin } from "@elizaos/plugin-sei";
Expand Down Expand Up @@ -175,6 +178,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;
Expand Down Expand Up @@ -461,7 +468,93 @@ export async function loadCharacters(
try {
const character: Character = await loadCharacterTryPath(
characterPath
);
);
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(
`Successfully loaded character from: ${resolvedPath}`)
loadedCharacters.push(character);
Comment on lines +555 to 558
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The loadedCharacters.push(character) operation appears twice in this block. This creates a duplicate entry of the same character in the array. The second push should be removed, keeping only the first one that follows the success log message.

Spotted by Graphite Reviewer

Is this helpful? React 👍 or 👎 to let us know.

} catch (e) {
process.exit(1);
Expand Down
11 changes: 10 additions & 1 deletion packages/core/src/runtime.ts
Original file line number Diff line number Diff line change
Expand Up @@ -400,11 +400,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);
Expand Down
Loading