Started implementing jar-based(plugin) tool loading

Core#plugin:
- Plugin.java: Main framework for plugins
- OllamaTool.java: Used for annotation based loading of tools
- InjectPlugin.java: Annoataion for injecting the Plugin instance, used when annotation based loading is used
- PluginMetadata.java: metadata of the plugin, a class-reflection of plugin.json file within each plugin
- PluginLoadingException.java: Exception thrown when an error happens durring plugin loading
This commit is contained in:
2025-05-28 01:11:07 +02:00
parent 8e44c11385
commit 603b72f2d3
13 changed files with 198 additions and 2 deletions

1
.idea/gradle.xml generated
View File

@@ -13,6 +13,7 @@
<option value="$PROJECT_DIR$/Core" />
<option value="$PROJECT_DIR$/Display" />
<option value="$PROJECT_DIR$/MALAPITool" />
<option value="$PROJECT_DIR$/Plugin" />
<option value="$PROJECT_DIR$/launcher" />
</set>
</option>

View File

@@ -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();
}
}
{
@@ -437,6 +450,32 @@ public class Core {
}
}
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.
* <p>

View File

@@ -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 {

View File

@@ -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;

View File

@@ -0,0 +1,5 @@
package me.zacharias.chat.plugin;
public class Plugin {
private final PluginMetadata metadata = null;
}

View File

@@ -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.
}
}

View File

@@ -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();
}

View File

@@ -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 {
}

View File

@@ -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;
}

View File

@@ -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);
}
}

View File

@@ -0,0 +1,6 @@
package plugin;
import me.zacharias.chat.plugin.Plugin;
public class Test extends Plugin {
}

View File

@@ -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;
}
}

View File

@@ -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());