Skip to content
Snippets Groups Projects
imported.js 5.71 KiB
Newer Older
  • Learn to ignore specific revisions
  • const fs = require("fs");
    const path = require("path");
    const { safeJsonParse } = require("../http");
    const { isWithin, normalizePath } = require("../files");
    const pluginsPath =
      process.env.NODE_ENV === "development"
        ? path.resolve(__dirname, "../../storage/plugins/agent-skills")
        : path.resolve(process.env.STORAGE_DIR, "plugins", "agent-skills");
    
    class ImportedPlugin {
      constructor(config) {
        this.config = config;
        this.handlerLocation = path.resolve(
          pluginsPath,
          this.config.hubId,
          "handler.js"
        );
        delete require.cache[require.resolve(this.handlerLocation)];
        this.handler = require(this.handlerLocation);
        this.name = config.hubId;
        this.startupConfig = {
          params: {},
        };
      }
    
      /**
       * Gets the imported plugin handler.
       * @param {string} hubId - The hub ID of the plugin.
       * @returns {ImportedPlugin} - The plugin handler.
       */
      static loadPluginByHubId(hubId) {
        const configLocation = path.resolve(
          pluginsPath,
          normalizePath(hubId),
          "plugin.json"
        );
        if (!this.isValidLocation(configLocation)) return;
        const config = safeJsonParse(fs.readFileSync(configLocation, "utf8"));
        return new ImportedPlugin(config);
      }
    
      static isValidLocation(pathToValidate) {
        if (!isWithin(pluginsPath, pathToValidate)) return false;
        if (!fs.existsSync(pathToValidate)) return false;
        return true;
      }
    
    
      /**
       * Checks if the plugin folder exists and if it does not, creates the folder.
       */
      static checkPluginFolderExists() {
        const dir = path.resolve(pluginsPath);
        if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
        return;
      }
    
    
      /**
       * Loads plugins from `plugins` folder in storage that are custom loaded and defined.
       * only loads plugins that are active: true.
       * @returns {Promise<string[]>} - array of plugin names to be loaded later.
       */
      static async activeImportedPlugins() {
        const plugins = [];
    
        this.checkPluginFolderExists();
    
        const folders = fs.readdirSync(path.resolve(pluginsPath));
        for (const folder of folders) {
          const configLocation = path.resolve(
            pluginsPath,
            normalizePath(folder),
            "plugin.json"
          );
          if (!this.isValidLocation(configLocation)) continue;
          const config = safeJsonParse(fs.readFileSync(configLocation, "utf8"));
          if (config.active) plugins.push(`@@${config.hubId}`);
        }
        return plugins;
      }
    
      /**
       * Lists all imported plugins.
       * @returns {Array} - array of plugin configurations (JSON).
       */
      static listImportedPlugins() {
        const plugins = [];
    
        this.checkPluginFolderExists();
    
        if (!fs.existsSync(pluginsPath)) return plugins;
    
        const folders = fs.readdirSync(path.resolve(pluginsPath));
        for (const folder of folders) {
          const configLocation = path.resolve(
            pluginsPath,
            normalizePath(folder),
            "plugin.json"
          );
          if (!this.isValidLocation(configLocation)) continue;
          const config = safeJsonParse(fs.readFileSync(configLocation, "utf8"));
          plugins.push(config);
        }
        return plugins;
      }
    
      /**
       * Updates a plugin configuration.
       * @param {string} hubId - The hub ID of the plugin.
       * @param {object} config - The configuration to update.
       * @returns {object} - The updated configuration.
       */
      static updateImportedPlugin(hubId, config) {
        const configLocation = path.resolve(
          pluginsPath,
          normalizePath(hubId),
          "plugin.json"
        );
        if (!this.isValidLocation(configLocation)) return;
    
        const currentConfig = safeJsonParse(
          fs.readFileSync(configLocation, "utf8"),
          null
        );
        if (!currentConfig) return;
    
        const updatedConfig = { ...currentConfig, ...config };
        fs.writeFileSync(configLocation, JSON.stringify(updatedConfig, null, 2));
        return updatedConfig;
      }
    
      /**
       * Validates if the handler.js file exists for the given plugin.
       * @param {string} hubId - The hub ID of the plugin.
       * @returns {boolean} - True if the handler.js file exists, false otherwise.
       */
      static validateImportedPluginHandler(hubId) {
        const handlerLocation = path.resolve(
          pluginsPath,
          normalizePath(hubId),
          "handler.js"
        );
        return this.isValidLocation(handlerLocation);
      }
    
      parseCallOptions() {
        const callOpts = {};
        if (!this.config.setup_args || typeof this.config.setup_args !== "object") {
          return callOpts;
        }
        for (const [param, definition] of Object.entries(this.config.setup_args)) {
          if (definition.required && !definition?.value) {
            console.log(
              `'${param}' required value for '${this.name}' plugin is missing. Plugin may not function or crash agent.`
            );
            continue;
          }
          callOpts[param] = definition.value || definition.default || null;
        }
        return callOpts;
      }
    
      plugin(runtimeArgs = {}) {
        const customFunctions = this.handler.runtime;
        return {
          runtimeArgs,
          name: this.name,
          config: this.config,
          setup(aibitat) {
            aibitat.function({
              super: aibitat,
              name: this.name,
              config: this.config,
              runtimeArgs: this.runtimeArgs,
              description: this.config.description,
              logger: aibitat?.handlerProps?.log || console.log, // Allows plugin to log to the console.
              introspect: aibitat?.introspect || console.log, // Allows plugin to display a "thought" the chat window UI.
              examples: this.config.examples ?? [],
              parameters: {
                $schema: "http://json-schema.org/draft-07/schema#",
                type: "object",
                properties: this.config.entrypoint.params ?? {},
                additionalProperties: false,
              },
              ...customFunctions,
            });
          },
        };
      }
    }
    
    module.exports = ImportedPlugin;