Class AbstractAsiContainer

All Implemented Interfaces:
PropertyChangeSource, Rebindable
Direct Known Subclasses:
AbstractSwingAsiContainer

public abstract class AbstractAsiContainer extends BasicPropertyChangeSource
A hybrid static/instance class for managing global and application-specific configurations.
  • Static methods provide access to the root Anahata AI working directory and its global subdirectories.
  • An instance of this class represents the configuration for a specific host application (e.g., "netbeans", "standalone"), managing its unique preferences and its own application-specific subdirectories.
Author:
anahata-gemini-pro-2.5
  • Field Details

    • sessionListeners

      private final Map<String, PropertyChangeListener> sessionListeners
      Map tracking active property change listeners per session to allow clean detaching on unregistration.
    • hostApplicationId

      private final String hostApplicationId
      The unique identifier for the host application (e.g., "netbeans", "standalone").

      Used to resolve application-specific subdirectories within the root Anahata working directory.

    • activeAgis

      private final List<Agi> activeAgis
      The list of currently active agi sessions managed by this container.
    • templates

      private final List<Agi> templates
      The list of reusable AGI session templates managed by this container.
    • providerRegistry

      private final Map<String, AbstractAiProvider> providerRegistry
      A master registry of AI provider instances.

      Keyed by the provider's unique UUID. These instances are shared across all sessions managed by this container.

    • executor

      private final ExecutorService executor
      A shared executor for container-level background tasks.
    • applicationAttributes

      public static Map applicationAttributes
      A JVM-scoped map for tools to store and share objects across all containers, sessions, and turns. This map is thread-safe.
    • containerAttributes

      public Map containerAttributes
      A container-scoped map for tools to store objects across all sessions and turns within this specific host application. This map is thread-safe.
    • notifications

      private final List<String> notifications
      Container-level operational notifications and boot diagnostic records.
    • keyWatcherRunning

      private volatile boolean keyWatcherRunning
      Flag controlling the execution loop of the background key file watcher daemon thread.
    • keyWatcherThread

      private Thread keyWatcherThread
      Dedicated daemon thread polling provider API key files on disk.
  • Constructor Details

    • AbstractAsiContainer

      public AbstractAsiContainer(String hostApplicationId)
      Creates a configuration instance for a specific host application. Upon instantiation, it loads the preferences and persisted providers for that application.
      Parameters:
      hostApplicationId - A unique identifier for the host application (e.g., "netbeans").
  • Method Details

    • loadProvidersFromDisk

      public int loadProvidersFromDisk()
      Scans the container's providers directory and loads all serialized AI provider entities.
      Returns:
      The number of providers loaded from disk.
    • addNotification

      public void addNotification(String notification)
      Adds an operational notification or startup diagnostic message.
      Parameters:
      notification - The diagnostic message to record.
    • clearNotifications

      public void clearNotifications()
      Clears all recorded operational notifications.
    • getNotifications

      public List<String> getNotifications()
      Gets an unmodifiable copy of all recorded operational notifications.
      Returns:
      List of notification messages.
    • getAllModels

      public List<AbstractModel> getAllModels(boolean providerEnabled, boolean modelEnabled)
      Gets all loaded models across all providers from memory.
      Parameters:
      providerEnabled - if true, filters to only effectively enabled providers (enabled with valid API keys).
      modelEnabled - if true, filters to only enabled models (model.isEnabled()).
      Returns:
      an aggregated list of matching models.
    • getAllModels

      public List<AbstractModel> getAllModels(boolean providerEnabled)
      Gets all loaded models across all providers from memory.
      Parameters:
      providerEnabled - if true, filters to only effectively enabled providers.
      Returns:
      an aggregated list of all models.
    • findModels

      public List<AbstractModel> findModels(String query, List<ResponseModality> modalities, boolean enabledOnly)
      Finds and filters models across providers matching a regex/text query AND all requested response modalities.
      Parameters:
      query - Optional regex or keyword query.
      modalities - Optional list of response modalities (e.g. [IMAGE, AUDIO]).
      enabledOnly - If true, filters to only effectively enabled providers (enabled with valid API keys).
      Returns:
      A list of matching AbstractModel instances across matching providers.
    • getProvider

      public AbstractAiProvider getProvider(String uuid)
      Retrieves a shared provider instance from the master registry by its UUID.

      Implementation details: This is the authoritative way to resolve providers. If the UUID is null, this method returns null.

      Parameters:
      uuid - The unique UUID of the provider instance.
      Returns:
      The shared provider instance, or null if not found.
    • getAllProviders

      public List<AbstractAiProvider> getAllProviders()
      Gets an unmodifiable list of all registered provider instances.
      Returns:
      All providers sorted by priority and display name.
    • registerProvider

      public void registerProvider(@NonNull @NonNull AbstractAiProvider provider)
      Registers a new provider instance in the master registry, binds its container reference, and persists it directly to disk in the container's providers directory.
      Parameters:
      provider - The provider instance to register.
    • unregisterProvider

      public void unregisterProvider(String uuid)
      Unregisters a provider instance, removing it from the registry and the persistent preferences.
      Parameters:
      uuid - The UUID of the provider to unregister.
    • getProviderByClass

      public <T extends AbstractAiProvider> T getProviderByClass(Class<T> providerClass)
      Finds a registered provider instance of a specific class.
      Type Parameters:
      T - The provider type.
      Parameters:
      providerClass - The class to look for.
      Returns:
      The first matching instance, or null if none registered.
    • getAsiCoreImplementationVersion

      public static String getAsiCoreImplementationVersion()
      Resolves the implementation version of the foundational Anahata ASI Core framework.

      Extracts the version from the package manifest of AbstractAsiContainer or from the Maven pom.properties packaged into the Core artifact at build time.

      Returns:
      The Core implementation version string, or null if running in an unpackaged test environment.
    • getMavenGroupId

      public String getMavenGroupId()
      Resolves the Maven Group ID of this container artifact.

      Defaults to "uno.anahata". Development Notice: This method is used in development mode to locate pom.properties on the classpath when running directly off target/classes.

      Returns:
      The Maven groupId string.
    • getMavenArtifactId

      public String getMavenArtifactId()
      Resolves the Maven Artifact ID of this specific host container module.

      Development Notice: Subclasses representing specific host environments (e.g. anahata-asi-desktop, anahata-asi-nb) should override this method so that getContainerImplementationVersion() can fish the build version from /META-INF/maven/<groupId>/<artifactId>/pom.properties when running directly off target/classes in development mode.

      Returns:
      The Maven artifactId string, or null if unknown.
    • getContainerImplementationVersion

      public String getContainerImplementationVersion()
      Resolves the implementation version of this specific host container artifact.

      Resolution Hierarchy:

      1. Queries getClass().getPackage().getImplementationVersion() (active in packaged production JARs and NBMs).
      2. In development mode (when running off target/classes), fishes the version from /META-INF/maven/<getMavenGroupId()>/<getMavenArtifactId()>/pom.properties on the classpath.
      3. Falls back to getAsiCoreImplementationVersion().
      Returns:
      The container implementation version string, or null if unresolved.
    • getContainerVersion

      public String getContainerVersion()
      Resolves the clean container version string used for the container directory.

      Discards any qualifiers or build timestamps after the first dash ('-'). For example, '1.2.0-20260904' or '1.2.0-SNAPSHOT' becomes '1.2.0'.

      Returns:
      The clean version string, or null if unresolved.
    • getDirectory

      public Path getDirectory() throws IOException
      Gets the root working directory for this specific host application instance. e.g., ~/.anahata/asi/netbeans or ~/.anahata/asi/netbeans/1.2.0
      Returns:
      The application-specific working directory path.
      Throws:
      IOException - If creating the directory fails.
    • getContainerCreationTime

      public Instant getContainerCreationTime()
      Retrieves the creation time of this container's directory from metadata or filesystem attributes.
      Returns:
      The creation Instant, or null if it could not be determined.
    • getProvidersDir

      public Path getProvidersDir() throws IOException
      Gets the directory where persisted AI provider entities are stored.
      Returns:
      The providers directory path.
      Throws:
      IOException - If creating the directory fails.
    • getUnloadableProvidersDir

      public Path getUnloadableProvidersDir() throws IOException
      Gets the directory where AI provider entities that failed to load are moved.
      Returns:
      The unloadable providers directory path.
      Throws:
      IOException - If creating the directory fails.
    • getTemplatesDir

      public Path getTemplatesDir() throws IOException
      Gets the directory where AGI session templates are stored.
      Returns:
      The templates directory path.
      Throws:
      IOException - If creating the directory fails.
    • getUnloadableTemplatesDir

      public Path getUnloadableTemplatesDir() throws IOException
      Gets the directory where AGI templates that failed to load are moved.
      Returns:
      The unloadable templates directory path.
      Throws:
      IOException - If creating the directory fails.
    • getAppDirSubDir

      public Path getAppDirSubDir(String name) throws IOException
      Gets a named subdirectory within this host application's working directory, creating it if it doesn't exist. e.g., ~/.anahata/asi/netbeans/sessions
      Parameters:
      name - The name of the subdirectory.
      Returns:
      The Path to the application-specific subdirectory.
      Throws:
      IOException - If creating the directory fails.
    • createNewAgiConfig

      public abstract AgiConfig createNewAgiConfig()
      Creates a new agi session blueprint. Overridden by concrete containers to provide product-specific configurations (e.g., NetBeansAgiConfig).
      Returns:
      The new agi configuration.
    • getEffectivelyEnabledProviders

      public List<AbstractAiProvider> getEffectivelyEnabledProviders()
      Gets an unmodifiable list of all registered AI providers that are effectively enabled (the provider is enabled and either does not require an API key or has valid keys configured).
      Returns:
      A list of effectively enabled providers.
    • hasAnyProviderConfigured

      public boolean hasAnyProviderConfigured()
      Checks if any of the AI providers configured in the global template have at least one valid API key.
      Returns:
      true if keys are configured, false otherwise.
    • createNewAgi

      public final Agi createNewAgi()
      Authoritatively creates, configures, registers, and opens a brand-new Agi session using the user's preferred template.
      Returns:
      The newly created and opened Agi session.
    • createNewBlankAgi

      public final Agi createNewBlankAgi()
      Authoritatively creates, configures, registers, and opens a brand-new Agi session using the clean framework config, bypassing any default template.
      Returns:
      The newly created and opened Agi session.
    • createNewAgi

      public final Agi createNewAgi(AgiConfig config)
      Authoritatively creates, configures, registers, and opens a brand-new Agi session with the provided configuration.

      Implementation details: This method orchestrates the creation, initial setup, pooling, and initial opening in one atomic weld. It is the primary entry point for spawning new intelligence instances.

      Parameters:
      config - The session configuration.
      Returns:
      The newly created and opened Agi session.
    • open

      public void open(@NonNull @NonNull Agi agi)
      authoritatively requests that the specified agi session be opened and brought to the front in the host UI.
      Parameters:
      agi - The agi session to open.
    • close

      public void close(@NonNull @NonNull Agi agi)
      Authoritatively requests that the specified agi session's UI tab or window be closed.
      Parameters:
      agi - The agi session to close.
    • getUI

      public abstract Object getUI(Agi agi)
      Retrieves the platform-specific UI component associated with an Agi session.
      Parameters:
      agi - The session.
      Returns:
      The UI component (e.g., AgiPanel or AgiTopComponent).
    • registerActiveAgi

      private void registerActiveAgi(Agi agi)
      Internal logic for session pooling and common hook invocation.
      Parameters:
      agi - The session to register.
    • cloneAgi

      public Agi cloneAgi(@NonNull @NonNull Agi agi, String newSessionId)
      Authoritatively clones an existing Agi session or template.

      Implementation details: Performs a deep clone using Kryo, assigns the new session ID, and updates the nickname. By placing this logic in the core container, we ensure architectural purity across different UI frameworks.

      Parameters:
      agi - The source Agi session to clone.
      newSessionId - Optional custom ID for the cloned session or template. If null or blank, a random UUID is generated.
      Returns:
      The newly cloned and registered Agi session.
    • cloneAgi

      public Agi cloneAgi(@NonNull @NonNull Agi agi)
      Authoritatively clones an existing Agi session or template with an auto-generated UUID.
      Parameters:
      agi - The source Agi session to clone.
      Returns:
      The newly cloned and registered Agi session.
    • unregisterAgi

      public void unregisterAgi(Agi agi)
      Unregisters a agi session from this configuration and triggers host-aware cleanup hooks.
      Parameters:
      agi - The agi session to unregister.
    • getActiveAgis

      public List<Agi> getActiveAgis()
      Gets an unmodifiable list of all active agi sessions.
      Returns:
      The list of active agis.
    • getAgi

      public Agi getAgi(String uuid)
      Retrieves an active Agi instance from the container pool by its unique UUID / session ID.
      Parameters:
      uuid - The unique UUID or session ID of the Agi instance.
      Returns:
      The matching Agi instance, or null if not found or if uuid is null.
    • getChildrenAgis

      public List<Agi> getChildrenAgis(String parentUuid)
      Retrieves all active Agi sessions spawned by a specific parent.
      Parameters:
      parentUuid - The UUID of the parent session.
      Returns:
      A list of child sessions.
    • getOpenAgis

      public List<Agi> getOpenAgis()
      Gets an unmodifiable list of all agi sessions that are currently logically open in the host UI.
      Returns:
      The list of open agis.
    • getSessionResourcesUnderPath

      public Map<Agi, List<Resource>> getSessionResourcesUnderPath(@NonNull @NonNull Path path)
      Resolves in-context resources under a given filesystem path across all open AGI sessions.
      Parameters:
      path - The filesystem path to check.
      Returns:
      A map of open AGI sessions to their matching resources (only sessions with matching items).
    • onAgiRegistered

      public void onAgiRegistered(Agi agi)
      Hook invoked whenever a session enters the active pool.

      Implementation details: Attaches a reactive bridge listener to the session and its ResourceManager. Whenever resources are registered or unregistered (via drag-and-drop, context menu, or AI tool calls), or when the session nickname or visibility changes, onSessionContextChanged(Agi, String) is invoked.

      Parameters:
      agi - The registered session.
    • onAgiUnregistered

      public void onAgiUnregistered(Agi agi)
      Hook invoked whenever a session is removed from the active pool.

      Implementation details: Detaches the reactive bridge listener from the session and its resource manager, then notifies onSessionContextChanged(Agi, String) to allow host IDEs to clear stale badges.

      Parameters:
      agi - The unregistered session.
    • onSessionContextChanged

      protected void onSessionContextChanged(Agi agi, String propertyName)
      Hook invoked whenever an AGI session's context, resources, nickname, or visibility state changes, or when a session is unregistered.

      Subclasses (such as NetBeans and IntelliJ containers) override this method to trigger host IDE project view tree repaints.

      Parameters:
      agi - The session whose state changed.
      propertyName - The property that changed ("resources", "nickname", "open", or null on unregistration).
    • configureNewAgi

      protected void configureNewAgi(Agi agi)
      Hook invoked to perform initial post-birth configuration of a new Agi.

      Implementation details: Applies the global default provider and model from preferences if they are configured.

      Parameters:
      agi - The new session.
    • onAgiOpened

      protected abstract void onAgiOpened(Agi agi)
      Hook invoked when a session has been logically opened.
      Parameters:
      agi - The opened session.
    • onAgiClosed

      protected abstract void onAgiClosed(Agi agi)
      Hook invoked when a session has been logically closed.
      Parameters:
      agi - The closed session.
    • getSessionsDir

      public Path getSessionsDir()
      Gets the directory where active agi sessions are stored.
      Returns:
      The sessions directory path.
    • getSavedSessionsDir

      public Path getSavedSessionsDir()
      Gets the directory where manually saved agi sessions are stored.
      Returns:
      The saved sessions directory path.
    • getDisposedSessionsDir

      public Path getDisposedSessionsDir()
      Gets the directory where disposed agi sessions are moved.
      Returns:
      The disposed sessions directory path.
    • getUnloadableSessionsDir

      public Path getUnloadableSessionsDir()
      Gets the directory where agi sessions that failed to load are moved.
      Returns:
      The unloadable sessions directory path.
    • ensureDir

      public static Path ensureDir(Path dir) throws IOException
      Ensures that a given directory exists on disk, creating it if missing.
      Parameters:
      dir - The directory path to verify and create.
      Returns:
      The verified directory path.
      Throws:
      IOException - If creating the directory fails.
    • getSubdirectory

      public static Path getSubdirectory(Path parent, String subdirName) throws IOException
      Resolves a named subdirectory against a parent path, ensuring it exists on disk.
      Parameters:
      parent - The parent directory path.
      subdirName - The name of the child directory.
      Returns:
      The verified subdirectory path.
      Throws:
      IOException - If directory creation fails.
    • autoSaveSession

      public void autoSaveSession(Agi agi, String reason) throws IOException
      Performs an automatic backup of the session using Kryo serialization.

      Implementation details: Only proceeds if the agi is in a stable state (IDLE, TOOL_PROMPT, etc.) to prevent serialization during volatile operations like streaming.

      Parameters:
      agi - The agi session to save.
      reason - A description of why the save was triggered.
      Throws:
      IOException
    • getPersistenceDir

      public Path getPersistenceDir(Agi agi) throws IOException
      Gets the primary persistence directory for the given AGI session or template.
      Parameters:
      agi - The AGI instance.
      Returns:
      The templates directory if template, or sessions directory if active session.
      Throws:
      IOException - If creating the directory fails.
    • saveAgi

      public void saveAgi(Agi agi) throws IOException
      Saves the AGI instance to its designated persistence directory.
      Parameters:
      agi - The AGI instance to save.
      Throws:
      IOException - If saving fails.
    • manualSaveSession

      public void manualSaveSession(Agi agi) throws IOException
      Manually saves the session to the 'saved' directory, or to templates if template.
      Parameters:
      agi - The agi session to save.
      Throws:
      IOException
    • saveAgiTo

      private void saveAgiTo(Agi agi, Path dir) throws IOException
      Serializes and saves an Agi session to a specific directory using Kryo. This method is synchronized on the Agi instance to prevent concurrent write issues.
      Parameters:
      agi - The Agi session to save.
      dir - The destination directory.
      Throws:
      IOException - If saving fails.
    • dispose

      public void dispose(Agi agi)
      Permanently disposes of a agi session or template, shutting it down and moving its serialized file to the 'disposed' directory.
      Parameters:
      agi - The agi session to dispose.
    • importSession

      public Agi importSession(Path path) throws IOException
      Imports an Agi session from an external file. The session is assigned a new ID to avoid collisions and registered as a new active agi.
      Parameters:
      path - The path to the serialized session file.
      Returns:
      The imported Agi session.
      Throws:
      IOException - If reading the session file fails.
    • loadAgi

      public Agi loadAgi(Path path)
      Deserializes an Agi instance from a file, rebinds it to this container, and automatically moves it to the 'unloadable' subdirectory on failure.
      Parameters:
      path - The path to the serialized AGI file.
      Returns:
      The loaded Agi instance, or null if loading failed.
    • loadAgis

      public int loadAgis(Path dir, Consumer<Agi> registrationCallback)
      Scans a directory for .kryo files in parallel, deserializes each via loadAgi(Path), and invokes the registration callback as each finishes loading.
      Parameters:
      dir - The directory to load from.
      registrationCallback - The callback to invoke for each successfully loaded AGI.
      Returns:
      The count of successfully loaded AGIs.
    • loadSessions

      public int loadSessions()
      Scans the sessions directory and loads all serialized active Agi sessions.
      Returns:
      The number of sessions loaded.
    • isTemplate

      public boolean isTemplate(@NonNull @NonNull Agi agi)
      Checks if the given session is an AGI template managed by this container.
      Parameters:
      agi - The AGI session to check.
      Returns:
      true if the session is registered in the templates list.
    • loadTemplatesFromDisk

      public int loadTemplatesFromDisk()
      Scans the templates directory and loads all serialized AGI templates into memory.
      Returns:
      The number of templates loaded from disk.
    • getTemplates

      public List<Agi> getTemplates()
      Gets an unmodifiable list of all registered AGI templates.
      Returns:
      List of AGI templates.
    • getDefaultTemplate

      public Agi getDefaultTemplate()
      Finds the default template whose session ID is "default" (case-insensitive).
      Returns:
      The default template, or null if none is configured.
    • registerTemplate

      public void registerTemplate(@NonNull @NonNull Agi templateAgi)
      Registers a template instance into the container's template registry and fires a property change event. If a template with the same session ID already exists, it is replaced.
      Parameters:
      templateAgi - The template to register.
    • unregisterTemplate

      public void unregisterTemplate(Agi templateAgi)
      Unregisters a template from the container's template registry and fires a property change event.
      Parameters:
      templateAgi - The template to unregister.
    • createNewAgiFromTemplate

      public Agi createNewAgiFromTemplate(String templateId)
      Authoritatively creates and opens a new active AGI session cloned directly from a template file on disk.
      Parameters:
      templateId - The unique ID of the template.
      Returns:
      The newly created, active, and opened AGI session.
    • createNewAgiFromTemplate

      public Agi createNewAgiFromTemplate(Agi templateAgi)
      Authoritatively creates and opens a new active AGI session cloned from a template instance.
      Parameters:
      templateAgi - The template to clone from.
      Returns:
      The newly created, active, and opened AGI session.
    • createTemplateFromSession

      public Agi createTemplateFromSession(@NonNull @NonNull Agi sessionAgi, @NonNull @NonNull String templateId) throws IOException
      Creates and saves a new template cloned from an active AGI session, preserving its nickname.
      Parameters:
      sessionAgi - The source session to base the template on.
      templateId - The unique template ID (used as sessionId and filename).
      Returns:
      The created template Agi.
      Throws:
      IOException - If saving fails.
    • createTemplate

      public Agi createTemplate(@NonNull @NonNull String templateId) throws IOException
      Creates, configures, and saves a fresh new template.
      Parameters:
      templateId - The unique template ID.
      Returns:
      The created template Agi.
      Throws:
      IOException - If saving fails.
    • startKeyFileWatcherThread

      private void startKeyFileWatcherThread()
      Starts a dedicated low-priority daemon thread that polls API key files once per second.

      This ensures that external edits to key files on disk are detected within 1 second without placing any synchronous disk I/O onto the Swing Event Dispatch Thread (EDT).

    • shutdown

      public void shutdown()
      Shuts down the container, interrupts the key watcher thread, and terminates the shared executor.
    • getWorkDir

      public static Path getWorkDir()
      Gets the root Anahata AI working directory (e.g., ~/.anahata/asi or $SNAP_USER_DATA/.anahata/asi).
      Returns:
      The root working directory path.
    • getWorkDirSubDir

      public static Path getWorkDirSubDir(String name)
      Gets a named subdirectory within the global root working directory, creating it if it doesn't exist. This is used for shared resources like provider configurations. e.g., ~/.anahata/asi/gemini
      Parameters:
      name - The name of the subdirectory.
      Returns:
      The Path object for the subdirectory.