package vip.mate.plugin.mem0; import org.slf4j.Logger; import vip.mate.plugin.api.MateClawPlugin; import vip.mate.plugin.api.PluginContext; import java.net.URI; /** * MateClaw plugin entrypoint that registers {@link Mem0Provider} with the * platform's memory subsystem. *

* Lifecycle: *

    *
  1. {@code onLoad} — read config from {@link PluginContext}, build * {@link Mem0Config} → {@link Mem0Client} → {@link Mem0Provider}, * then {@code context.registerMemoryProvider(provider)}. * If the config is incomplete (no baseUrl), the provider is registered * but reports {@code isAvailable()=false} — the platform silently * skips it.
  2. *
  3. {@code onEnable} / {@code onDisable} — lifecycle log only.
  4. *
* *

This plugin is NOT part of the default stack. Users must: *

    *
  1. Self-host a Mem0 service (FastAPI + pgvector + optional Neo4j)
  2. *
  3. Drop the built JAR into the platform's {@code plugins/} directory
  4. *
  5. Configure {@code baseUrl} (and optionally {@code apiKey}) via the * plugin admin UI
  6. *
* * @author MateClaw Team */ public class Mem0Plugin implements MateClawPlugin { private static final String CONFIG_BASE_URL = "baseUrl"; private static final String CONFIG_API_KEY = "apiKey"; private static final String CONFIG_SEARCH_ENABLED = "searchEnabled"; private static final String CONFIG_SYNC_ENABLED = "syncEnabled"; private static final String CONFIG_MAX_RESULTS = "maxResults"; private static final String CONFIG_TIMEOUT_MS = "timeoutMs"; private static final String CONFIG_SYNC_QUEUE_CAPACITY = "syncQueueCapacity"; private Logger log; @Override public void onLoad(PluginContext context) { this.log = context.getLogger(); Mem0Config config = readConfig(context); if (!config.isUsable()) { log.warn("Mem0 plugin loaded without baseUrl — provider will stay unavailable. " + "Configure 'baseUrl' in the plugin config to enable."); } Mem0Client client = new Mem0Client(config); Mem0Provider provider = new Mem0Provider(config, client, log); try { context.registerMemoryProvider(provider); } catch (RuntimeException e) { provider.close(); throw e; } log.info("Mem0 plugin loaded: baseUrl={}, searchEnabled={}, syncEnabled={}, maxResults={}, timeoutMs={}, syncQueueCapacity={}", maskUrl(config.baseUrl()), config.searchEnabled(), config.syncEnabled(), config.maxResults(), config.timeoutMs(), config.syncQueueCapacity()); } @Override public void onEnable() { if (log != null) log.info("Mem0 plugin enabled"); } @Override public void onDisable() { if (log != null) log.info("Mem0 plugin disabled"); } private Mem0Config readConfig(PluginContext ctx) { String baseUrl = ctx.getConfig(CONFIG_BASE_URL, String.class); String apiKey = ctx.getConfig(CONFIG_API_KEY, String.class); Boolean searchEnabled = ctx.getConfig(CONFIG_SEARCH_ENABLED, Boolean.class); Boolean syncEnabled = ctx.getConfig(CONFIG_SYNC_ENABLED, Boolean.class); Integer maxResults = ctx.getConfig(CONFIG_MAX_RESULTS, Integer.class); Integer timeoutMs = ctx.getConfig(CONFIG_TIMEOUT_MS, Integer.class); Integer syncQueueCapacity = ctx.getConfig(CONFIG_SYNC_QUEUE_CAPACITY, Integer.class); return new Mem0Config( baseUrl, apiKey, searchEnabled == null ? true : searchEnabled, syncEnabled == null ? true : syncEnabled, maxResults == null ? Mem0Config.DEFAULT_MAX_RESULTS : maxResults, timeoutMs == null ? Mem0Config.DEFAULT_TIMEOUT_MS : timeoutMs, syncQueueCapacity == null ? Mem0Config.DEFAULT_SYNC_QUEUE_CAPACITY : Math.max(1, syncQueueCapacity) ); } /** * Mask credentials in the URL when logging. Keeps the scheme + host, * strips any user info and path. */ private static String maskUrl(String url) { if (url == null || url.isBlank()) return "(unset)"; try { URI u = URI.create(url); String host = u.getHost(); int port = u.getPort(); return u.getScheme() + "://" + host + (port > 0 ? ":" + port : ""); } catch (Exception e) { return "(malformed)"; } } }