diff --git a/.idea/gradle.xml b/.idea/gradle.xml index 88d702b..b1c23af 100644 --- a/.idea/gradle.xml +++ b/.idea/gradle.xml @@ -13,6 +13,7 @@ + diff --git a/Core/src/main/java/me/zacharias/chat/core/Core.java b/Core/src/main/java/me/zacharias/chat/core/Core.java index 548ab05..769a02d 100644 --- a/Core/src/main/java/me/zacharias/chat/core/Core.java +++ b/Core/src/main/java/me/zacharias/chat/core/Core.java @@ -5,12 +5,17 @@ import me.zacharias.chat.core.memory.GetMemoryFunction; import me.zacharias.chat.core.memory.RemoveMemoryFunction; import me.zacharias.chat.ollama.*; import me.zacharias.chat.ollama.exceptions.OllamaToolErrorException; +import me.zacharias.chat.plugin.Plugin; +import me.zacharias.chat.plugin.PluginLoader; +import me.zacharias.chat.plugin.exceptions.PluginLoadingException; import org.intellij.lang.annotations.MagicConstant; import org.json.JSONArray; import org.json.JSONObject; import java.io.*; import java.net.*; +import java.nio.file.FileSystem; +import java.nio.file.FileSystems; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; import java.util.ArrayList; @@ -69,6 +74,7 @@ public class Core { public static final String DATA; public static final File DATA_DIR; + public static final File PLUGIN_DIRECTORY; static { String data; @@ -98,6 +104,13 @@ public class Core { if(!DATA_DIR.exists()) { DATA_DIR.mkdirs(); } + + String pluginDir = DATA + "/plugins"; + PLUGIN_DIRECTORY = new File(pluginDir); + + if(!PLUGIN_DIRECTORY.exists()) { + PLUGIN_DIRECTORY.mkdirs(); + } } { @@ -436,7 +449,33 @@ public class Core { throw new RuntimeException(e); } } - + + public void enablePlugins(File pluginDirectory) { + if(!pluginDirectory.exists()) { + throw new IllegalArgumentException("Plugin directory does not exist"); + } + if(!pluginDirectory.isDirectory()) { + throw new IllegalArgumentException("Plugin directory is not a directory"); + } + File[] files = pluginDirectory.listFiles((dir, name) -> name.endsWith(".jar")); + if(files == null) { + return; + } + PluginLoader loader = new PluginLoader(); + for(File file : files) { + try(FileSystem fs = FileSystems.newFileSystem(file.toPath())){ + if(!fs.getPath("/plugin.json").toFile().exists()) + { + throw new PluginLoadingException("Plugin does not contain a plugin.json file", file.getName()); + } + JSONObject pluginJson = new JSONObject(new String(fs.getPath("/plugin.json").toFile().readAllBytes())); + Plugin plugin = loader.loadPlugin(pluginJson, fs); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + } + /** * Represents the source of a tool. *
diff --git a/Core/src/main/java/me/zacharias/chat/ollama/OllamaFunctionTool.java b/Core/src/main/java/me/zacharias/chat/ollama/OllamaFunctionTool.java index 147f424..2d781dd 100644 --- a/Core/src/main/java/me/zacharias/chat/ollama/OllamaFunctionTool.java +++ b/Core/src/main/java/me/zacharias/chat/ollama/OllamaFunctionTool.java @@ -5,7 +5,7 @@ import me.zacharias.chat.ollama.exceptions.OllamaToolErrorException; import org.json.JSONObject; /** - * Represents a tool that can be called by Ollama. + * Represents a tool that Ollama can call. */ public abstract class OllamaFunctionTool implements OllamaTool { diff --git a/Core/src/main/java/me/zacharias/chat/ollama/OllamaObject.java b/Core/src/main/java/me/zacharias/chat/ollama/OllamaObject.java index 34a527c..8cf3a7f 100644 --- a/Core/src/main/java/me/zacharias/chat/ollama/OllamaObject.java +++ b/Core/src/main/java/me/zacharias/chat/ollama/OllamaObject.java @@ -1,5 +1,6 @@ package me.zacharias.chat.ollama; +import com.sun.source.util.Plugin; import me.zacharias.chat.core.Core; import me.zacharias.chat.core.LaunchOptions; import me.zacharias.chat.core.files.FileHandlerLocation; @@ -12,6 +13,10 @@ import org.json.JSONObject; import java.io.BufferedReader; import java.io.File; import java.io.FileReader; +import java.nio.file.FileSystem; +import java.nio.file.FileSystems; +import java.nio.file.Files; +import java.nio.file.Path; import java.util.ArrayList; import java.util.HashMap; import java.util.List; diff --git a/Core/src/main/java/me/zacharias/chat/plugin/Plugin.java b/Core/src/main/java/me/zacharias/chat/plugin/Plugin.java new file mode 100644 index 0000000..e06a9d9 --- /dev/null +++ b/Core/src/main/java/me/zacharias/chat/plugin/Plugin.java @@ -0,0 +1,5 @@ +package me.zacharias.chat.plugin; + +public class Plugin { + private final PluginMetadata metadata = null; +} diff --git a/Core/src/main/java/me/zacharias/chat/plugin/PluginLoader.java b/Core/src/main/java/me/zacharias/chat/plugin/PluginLoader.java new file mode 100644 index 0000000..20bed0e --- /dev/null +++ b/Core/src/main/java/me/zacharias/chat/plugin/PluginLoader.java @@ -0,0 +1,62 @@ +package me.zacharias.chat.plugin; + +import org.json.JSONObject; + +import java.lang.reflect.Field; +import java.nio.file.FileSystem; +import java.nio.file.Path; + +public class PluginLoader { + + public PluginLoader() { + } + + public Plugin loadPlugin(FileSystem pluginJar, JSONObject pluginMeta) throws ClassNotFoundException, IllegalAccessException, InstantiationException { + String pluginName = pluginMeta.getString("name"); + String pluginEntryPoint = pluginMeta.getString("entryPoint"); + String pluginVersion = pluginMeta.getString("version"); + String pluginDescription = pluginMeta.optString("description", "No description provided"); + String[] pluginAuthors = pluginMeta.optJSONArray("author") != null ? + pluginMeta.getJSONArray("author").toList().toArray(new String[0]) : new String[0]; + + PluginMetadata pluginMetadata = new PluginMetadata() { + @Override + public String getName() { + return pluginName; + } + + @Override + public String getVersion() { + return pluginVersion; + } + + @Override + public String getDescription() { + return pluginDescription; + } + + @Override + public String[] getAuthor() { + return pluginAuthors; + } + + @Override + public String entryPoint() { + return pluginEntryPoint; + } + }; + + try { + Plugin plugin = (Plugin) Class.forName(pluginEntryPoint).newInstance(); + Field metadataField = plugin.getClass().getDeclaredField("metadata"); + metadataField.set(plugin, pluginMetadata); + if (metadataField.get(plugin) == null) + throw new IllegalStateException("Plugin metadata field is null for plugin: " + pluginName); + }catch (NoSuchFieldException e) + { + throw new IllegalStateException("Plugin class " + pluginEntryPoint + " does not have a 'metadata' field", e); + } + return null; + //ClassLoader classLoader = pluginJar. + } +} diff --git a/Core/src/main/java/me/zacharias/chat/plugin/PluginMetadata.java b/Core/src/main/java/me/zacharias/chat/plugin/PluginMetadata.java new file mode 100644 index 0000000..f2a0460 --- /dev/null +++ b/Core/src/main/java/me/zacharias/chat/plugin/PluginMetadata.java @@ -0,0 +1,9 @@ +package me.zacharias.chat.plugin; + +public interface PluginMetadata { + public String getName(); + public String getVersion(); + public String getDescription(); + public String[] getAuthor(); + public String entryPoint(); +} diff --git a/Core/src/main/java/me/zacharias/chat/plugin/annotation/OllamaTool.java b/Core/src/main/java/me/zacharias/chat/plugin/annotation/OllamaTool.java new file mode 100644 index 0000000..b0fe1d6 --- /dev/null +++ b/Core/src/main/java/me/zacharias/chat/plugin/annotation/OllamaTool.java @@ -0,0 +1,12 @@ +package me.zacharias.chat.plugin.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.TYPE) +public @interface OllamaTool { + +} diff --git a/Core/src/main/java/me/zacharias/chat/plugin/annotation/injectons/InjectPlugin.java b/Core/src/main/java/me/zacharias/chat/plugin/annotation/injectons/InjectPlugin.java new file mode 100644 index 0000000..d18b9b3 --- /dev/null +++ b/Core/src/main/java/me/zacharias/chat/plugin/annotation/injectons/InjectPlugin.java @@ -0,0 +1,14 @@ +package me.zacharias.chat.plugin.annotation.injectons; + +import me.zacharias.chat.plugin.Plugin; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.FIELD, ElementType.PARAMETER, ElementType.METHOD}) +public @interface InjectPlugin { + Class extends Plugin> classType() default Plugin.class; +} diff --git a/Core/src/main/java/me/zacharias/chat/plugin/exceptions/PluginLoadingException.java b/Core/src/main/java/me/zacharias/chat/plugin/exceptions/PluginLoadingException.java new file mode 100644 index 0000000..b9466e9 --- /dev/null +++ b/Core/src/main/java/me/zacharias/chat/plugin/exceptions/PluginLoadingException.java @@ -0,0 +1,7 @@ +package me.zacharias.chat.plugin.exceptions; + +public class PluginLoadingException extends RuntimeException { + public PluginLoadingException(String message, String pluginName) { + super("Plugin: \""+pluginName+"\"> "+message); + } +} diff --git a/Core/src/test/java/plugin/Test.java b/Core/src/test/java/plugin/Test.java new file mode 100644 index 0000000..6fdb2b2 --- /dev/null +++ b/Core/src/test/java/plugin/Test.java @@ -0,0 +1,6 @@ +package plugin; + +import me.zacharias.chat.plugin.Plugin; + +public class Test extends Plugin { +} diff --git a/Core/src/test/java/plugin/Tool.java b/Core/src/test/java/plugin/Tool.java new file mode 100644 index 0000000..2129e0a --- /dev/null +++ b/Core/src/test/java/plugin/Tool.java @@ -0,0 +1,34 @@ +package plugin; + +import me.zacharias.chat.ollama.OllamaFunctionArgument; +import me.zacharias.chat.ollama.OllamaFunctionTool; +import me.zacharias.chat.ollama.OllamaPerameter; +import me.zacharias.chat.ollama.OllamaToolRespnce; +import me.zacharias.chat.plugin.annotation.OllamaTool; +import me.zacharias.chat.plugin.annotation.injectons.InjectPlugin; + +@OllamaTool +public class Tool extends OllamaFunctionTool { + @InjectPlugin(classType = Test.class) + Test core; + + @Override + public String name() { + return ""; + } + + @Override + public String description() { + return ""; + } + + @Override + public OllamaPerameter parameters() { + return null; + } + + @Override + public OllamaToolRespnce function(OllamaFunctionArgument... args) { + return null; + } +} diff --git a/Display/src/main/java/me/zacharias/chat/display/Display.java b/Display/src/main/java/me/zacharias/chat/display/Display.java index 0444c53..8023650 100644 --- a/Display/src/main/java/me/zacharias/chat/display/Display.java +++ b/Display/src/main/java/me/zacharias/chat/display/Display.java @@ -53,6 +53,8 @@ public class Display { //.addFileTools(FileHandlerLocation.DATA_FILES) .build()); + core.enablePlugins(Core.PLUGIN_DIRECTORY); + core.addTool(new TimeTool(), Core.Source.INTERNAL); //core.addTool(new PythonRunner(core), Core.Source.INTERNAL); core.addTools(new MALAPITool().getOllamaTools());