diff --git a/dev/src/main/java/com/google/adk/web/service/RunnerService.java b/dev/src/main/java/com/google/adk/web/service/RunnerService.java index 9372789a5..7297af833 100644 --- a/dev/src/main/java/com/google/adk/web/service/RunnerService.java +++ b/dev/src/main/java/com/google/adk/web/service/RunnerService.java @@ -19,14 +19,18 @@ import com.google.adk.agents.BaseAgent; import com.google.adk.artifacts.BaseArtifactService; import com.google.adk.memory.BaseMemoryService; +import com.google.adk.plugins.BasePlugin; import com.google.adk.runner.Runner; import com.google.adk.sessions.BaseSessionService; import com.google.adk.web.AgentLoader; +import com.google.common.collect.ImmutableList; +import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.http.HttpStatus; import org.springframework.stereotype.Component; import org.springframework.web.server.ResponseStatusException; @@ -40,18 +44,21 @@ public class RunnerService { private final BaseArtifactService artifactService; private final BaseSessionService sessionService; private final BaseMemoryService memoryService; + private final List extraPlugins; private final Map runnerCache = new ConcurrentHashMap<>(); - @Autowired public RunnerService( AgentLoader agentProvider, BaseArtifactService artifactService, BaseSessionService sessionService, - BaseMemoryService memoryService) { + BaseMemoryService memoryService, + @Autowired(required = false) @Qualifier("extraPlugins") List extraPlugins) { this.agentProvider = agentProvider; this.artifactService = artifactService; this.sessionService = sessionService; this.memoryService = memoryService; + this.extraPlugins = + extraPlugins != null ? ImmutableList.copyOf(extraPlugins) : ImmutableList.of(); } /** @@ -72,7 +79,12 @@ public Runner getRunner(String appName) { appName, agent.name()); return new Runner( - agent, appName, this.artifactService, this.sessionService, this.memoryService); + agent, + appName, + this.artifactService, + this.sessionService, + this.memoryService, + this.extraPlugins); } catch (java.util.NoSuchElementException e) { log.error( "Agent/App named '{}' not found in registry. Available apps: {}", diff --git a/maven_plugin/src/main/java/com/google/adk/maven/WebMojo.java b/maven_plugin/src/main/java/com/google/adk/maven/WebMojo.java index 351d0c28c..a5beb3ca3 100644 --- a/maven_plugin/src/main/java/com/google/adk/maven/WebMojo.java +++ b/maven_plugin/src/main/java/com/google/adk/maven/WebMojo.java @@ -16,8 +16,12 @@ package com.google.adk.maven; +import autovalue.shaded.com.google.common.collect.ImmutableList; +import com.google.adk.plugins.BasePlugin; import com.google.adk.utils.ComponentRegistry; import com.google.adk.web.AdkWebServer; +import com.google.adk.web.AgentLoader; +import com.google.common.base.Strings; import java.io.IOException; import java.lang.reflect.Field; import java.net.MalformedURLException; @@ -180,6 +184,30 @@ public class WebMojo extends AbstractMojo { @Parameter(property = "registry") private String registry; + /** + * Comma-separated list of additional plugin classes to load. + * + *

This parameter allows users to specify extra plugins that extend BasePlugin. Plugins provide + * global callbacks for logging, monitoring, replay, caching, or modifying agent behaviors. These + * are additional to any plugins configured by the user's code. + * + *

Each plugin reference can be either: + * + *

    + *
  • Static field reference: {@code com.example.MyPlugin.INSTANCE} + *
  • Class name: {@code com.example.MyPlugin} (requires default constructor) + *
+ * + *

Example: + * + *

{@code
+   * mvn google-adk:web -Dagents=... -DextraPlugins=com.google.adk.plugins.ReplayPlugin
+   * mvn google-adk:web -Dagents=... -DextraPlugins=com.google.adk.plugins.ReplayPlugin,com.example.CustomPlugin
+   * }
+ */ + @Parameter(property = "extraPlugins") + private String extraPlugins; + private ConfigurableApplicationContext applicationContext; private URLClassLoader projectClassLoader; @@ -205,7 +233,10 @@ public void execute() throws MojoExecutionException, MojoFailureException { // Load and instantiate the AgentLoader getLog().info("Loading agent loader: " + agents); - com.google.adk.web.AgentLoader provider = loadAgentProvider(); + AgentLoader provider = loadAgentProvider(); + + // Load extra plugins if specified + final List extraPluginInstances = loadExtraPlugins(); // Set up system properties for Spring Boot setupSystemProperties(); @@ -213,10 +244,11 @@ public void execute() throws MojoExecutionException, MojoFailureException { // Start the Spring Boot application with custom agent provider SpringApplication app = new SpringApplication(AdkWebServer.class); - // Add the agent provider as a bean + // Add the agent provider and extra plugins as beans app.addInitializers( ctx -> { ctx.getBeanFactory().registerSingleton("agentLoader", provider); + ctx.getBeanFactory().registerSingleton("extraPlugins", extraPluginInstances); }); getLog().info("Starting Spring Boot application..."); @@ -268,6 +300,7 @@ private void logConfiguration() { getLog().info(" Server Host: " + host); getLog().info(" Server Port: " + port); getLog().info(" Registry: " + (registry != null ? registry : "default")); + getLog().info(" Extra Plugins: " + (extraPlugins != null ? extraPlugins : "none")); } private void setupSystemProperties() { @@ -401,7 +434,7 @@ private T tryLoadFromConstructor(String className, Class expectedType) } } - private com.google.adk.web.AgentLoader loadAgentProvider() throws MojoExecutionException { + private AgentLoader loadAgentProvider() throws MojoExecutionException { // First, check if agents parameter is a directory path Path agentsPath = Paths.get(agents); if (Files.isDirectory(agentsPath)) { @@ -411,15 +444,56 @@ private com.google.adk.web.AgentLoader loadAgentProvider() throws MojoExecutionE // Next, try to interpret as class.field syntax if (agents.contains(".")) { - com.google.adk.web.AgentLoader provider = - tryLoadFromStaticField(agents, com.google.adk.web.AgentLoader.class); + AgentLoader provider = tryLoadFromStaticField(agents, AgentLoader.class); if (provider != null) { return provider; } } // Fallback to trying the entire string as a class name - return tryLoadFromConstructor(agents, com.google.adk.web.AgentLoader.class); + return tryLoadFromConstructor(agents, AgentLoader.class); + } + + /** + * Loads extra plugins from the comma-separated extraPlugins parameter. + * + * @return List of loaded plugin instances + * @throws MojoExecutionException if plugins cannot be loaded + */ + private ImmutableList loadExtraPlugins() throws MojoExecutionException { + ImmutableList.Builder plugins = ImmutableList.builder(); + String[] pluginNames = Strings.nullToEmpty(extraPlugins).split(","); + + for (String name : pluginNames) { + name = name.trim(); + if (name.isEmpty()) { + continue; + } + + getLog().info("Loading plugin: " + name); + + // Try to load as static field first + BasePlugin plugin = null; + if (name.contains(".")) { + plugin = tryLoadFromStaticField(name, BasePlugin.class); + } + + // Fallback to constructor + if (plugin == null) { + plugin = tryLoadFromConstructor(name, BasePlugin.class); + } + + if (plugin == null) { + throw new MojoExecutionException("Failed to load plugin: " + name); + } + + plugins.add(plugin); + getLog() + .info( + String.format("Successfully loaded plugin: `%s` from `%s`", plugin.getName(), name)); + } + + return plugins.build(); } /** Cleans up all resources including application context, classloader. */