Newer
Older
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
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();
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
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;