diff --git a/app/MindWork AI Studio/Tools/PluginSystem/NoPlugin.cs b/app/MindWork AI Studio/Tools/PluginSystem/NoPlugin.cs
index ab08241f..a6030603 100644
--- a/app/MindWork AI Studio/Tools/PluginSystem/NoPlugin.cs
+++ b/app/MindWork AI Studio/Tools/PluginSystem/NoPlugin.cs
@@ -7,4 +7,4 @@ namespace AIStudio.Tools.PluginSystem;
///
/// The Lua state that the plugin was loaded into.
/// The error message that occurred while parsing the plugin.
-public sealed class NoPlugin(LuaState state, string parsingError) : PluginBase(state, PluginType.NONE, parsingError);
\ No newline at end of file
+public sealed class NoPlugin(LuaState state, string parsingError) : PluginBase(string.Empty, state, PluginType.NONE, parsingError);
\ No newline at end of file
diff --git a/app/MindWork AI Studio/Tools/PluginSystem/PluginBase.cs b/app/MindWork AI Studio/Tools/PluginSystem/PluginBase.cs
index 1553f74a..71549b33 100644
--- a/app/MindWork AI Studio/Tools/PluginSystem/PluginBase.cs
+++ b/app/MindWork AI Studio/Tools/PluginSystem/PluginBase.cs
@@ -78,7 +78,7 @@ public abstract class PluginBase
///
public bool IsValid => this is not NoPlugin && this.baseIssues.Count == 0 && this.pluginIssues.Count == 0;
- protected PluginBase(LuaState state, PluginType type, string parseError = "")
+ protected PluginBase(string path, LuaState state, PluginType type, string parseError = "")
{
this.state = state;
this.Type = type;
@@ -93,6 +93,9 @@ public abstract class PluginBase
this.state.OpenMathLibrary();
this.state.OpenBitwiseLibrary();
this.state.OpenCoroutineLibrary();
+
+ // Add the module loader so that the plugin can load other Lua modules:
+ this.state.ModuleLoader = new PluginLoader(path);
var issues = new List();
if(!string.IsNullOrWhiteSpace(parseError))
diff --git a/app/MindWork AI Studio/Tools/PluginSystem/PluginFactory.cs b/app/MindWork AI Studio/Tools/PluginSystem/PluginFactory.cs
index 9b1d1ebb..054d4e8d 100644
--- a/app/MindWork AI Studio/Tools/PluginSystem/PluginFactory.cs
+++ b/app/MindWork AI Studio/Tools/PluginSystem/PluginFactory.cs
@@ -90,7 +90,7 @@ public static class PluginFactory
}
- public static async Task Load(string code, CancellationToken cancellationToken = default)
+ public static async Task Load(string path, string code, CancellationToken cancellationToken = default)
{
var state = LuaState.Create();
@@ -114,7 +114,7 @@ public static class PluginFactory
return type switch
{
- PluginType.LANGUAGE => new PluginLanguage(state, type),
+ PluginType.LANGUAGE => new PluginLanguage(path, state, type),
_ => new NoPlugin(state, "This plugin type is not supported yet. Please try again with a future version of AI Studio.")
};
diff --git a/app/MindWork AI Studio/Tools/PluginSystem/PluginLoader.cs b/app/MindWork AI Studio/Tools/PluginSystem/PluginLoader.cs
new file mode 100644
index 00000000..ec81f73c
--- /dev/null
+++ b/app/MindWork AI Studio/Tools/PluginSystem/PluginLoader.cs
@@ -0,0 +1,48 @@
+using System.Text;
+
+using AIStudio.Settings;
+
+using Lua;
+
+namespace AIStudio.Tools.PluginSystem;
+
+///
+/// Loads Lua modules from a plugin directory.
+///
+///
+/// Any plugin can load Lua modules from its own directory. This class is used to load these modules.
+/// Loading other modules outside the plugin directory is not allowed.
+///
+/// The directory where the plugin is located.
+public sealed class PluginLoader(string pluginDirectory) : ILuaModuleLoader
+{
+ private static readonly string PLUGIN_BASE_PATH = Path.Join(SettingsManager.DataDirectory, "plugins");
+
+ #region Implementation of ILuaModuleLoader
+
+ ///
+ public bool Exists(string moduleName)
+ {
+ // Ensure that the user doesn't try to escape the plugin directory:
+ if (moduleName.Contains("..") || pluginDirectory.Contains(".."))
+ return false;
+
+ // Ensure that the plugin directory is nested in the plugin base path:
+ if (!pluginDirectory.StartsWith(PLUGIN_BASE_PATH, StringComparison.OrdinalIgnoreCase))
+ return false;
+
+ var path = Path.Join(pluginDirectory, $"{moduleName}.lua");
+ return File.Exists(path);
+ }
+
+ ///
+ public async ValueTask LoadAsync(string moduleName, CancellationToken cancellationToken = default)
+ {
+ var path = Path.Join(pluginDirectory, $"{moduleName}.lua");
+ var code = await File.ReadAllTextAsync(path, Encoding.UTF8, cancellationToken);
+
+ return new(moduleName, code);
+ }
+
+ #endregion
+}
\ No newline at end of file