Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 61 additions & 0 deletions docs/PLUGIN_API_CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,67 @@ milestone. **[verified]** = read from the checked-in ABI dump. **[reconstructed]
= diffed from `plugin-api/src` history (predates the dump; symbol-accurate).

### 26.41 — unreleased
- **added — Tool-source groups and health, backend model names, and change listeners** _(ADFA-6278)_ **[verified]**
A consumer such as the agent's chat screen could not tell which tools the agent has, whether
they work, or which model will answer, and was never told when any of that changed.
`ToolSourceRegistry.CONTRACT_VERSION` is now `2`.
- `ToolSourceRegistry.GroupedToolSource` (new interface, extends `ToolSource`):
`getToolGroups()` splits one source into `ToolSourceRegistry.ToolGroup`s (`getId`,
`getDisplayName`, `getToolNames`, `getStatus`, `getStatusMessage`) — one per MCP server,
say. A group may name no tools, which is how an unreachable server keeps its place.
- `ToolSourceRegistry.StatusReportingToolSource` (new interface, extends `ToolSource`):
`getStatus()` and `getStatusMessage()` report health as a `CapabilityStatus`:
`AVAILABLE`, `CONNECTING` or `DEGRADED`. Both must be cheap and non-blocking, because
consumers call them on the UI thread. A provider reports a change with
`ToolSourceRegistry.notifyToolSourceStatusChanged`. The enum may grow; handle an unknown
constant as `DEGRADED`. A source that does not implement it reads as `AVAILABLE`.
- `ToolSourceRegistry.addToolSourceListener` / `removeToolSourceListener` take a
`ToolSourceListener`. `onToolSourcesChanged` is told the provider id on register,
unregister and `notifyToolsChanged`; `onToolSourceStatusChanged` on
`notifyToolSourceStatusChanged`, so a status change need not re-read the tool list. It
defaults to `onToolSourcesChanged`.
- `LlmInferenceService.ActiveModelReportingBackend` (new interface, extends `LlmBackend`):
`getActiveModelName()` names the model a backend will answer with. It is not called
`getModelName` because the Gemini and OpenAI backends already declare a private
`getModelName()`. A backend that does not implement it names no model.
- `LlmInferenceService.StatusReportingBackend` (new interface, extends `LlmBackend`):
`getStatus()` and `getStatusMessage()` report whether the backend can answer right now as
the same `CapabilityStatus`. A backend
that does not implement it reads as `AVAILABLE`. `isAvailable()` still means only "set up"; a
configured OpenAI-compatible server that is not running is `DEGRADED`. Same cheap,
non-blocking rule and unknown-constant reading as for a tool source.
- `com.itsaky.androidide.plugins.services.CapabilityStatus` (new top-level enum) is the one
status both contracts return, so a consumer showing backends and tool sources side by side
maps one vocabulary, not two identical enums.
- `com.itsaky.androidide.plugins.ai.LlmBackendRegistration` keeps a backend plugin's
`LlmBackend` registered with the router: `start(backend)` from `activate`, `stop()` from
`deactivate` and `dispose`. It re-registers when AI Core restarts, and calls
`notifyBackendChanged` when one of the `watchedKeys` in the plugin's settings changes, so
backend plugins stop carrying their own copy of that wiring.
- `LlmInferenceService.addBackendChangeListener` / `removeBackendChangeListener` take a
`BackendChangeListener`, which is told the backend id on register, unregister, a change
in the user's selection, and `LlmInferenceService.notifyBackendChanged`. A backend calls
that when its availability or model changes.
- `LlmInferenceService.EmbeddingModelSelectable` (new interface, extends `EmbeddingBackend`)
lets a screen outside the backend's plugin list (`listEmbeddingModels()`) and change
(`setEmbeddingModelId`) the embedding model; the backend still stores the choice and calls
`notifyBackendChanged`. `EmbeddingBackend.getEmbeddingDimensions()` is now constant per
model rather than per backend instance.

Listeners are called synchronously on the changing thread, outside the implementation's
lock. An exception a listener throws is contained.

Status, model name and groups are new interfaces rather than defaults on `ToolSource` and
`LlmBackend`, per the "prefer a new interface" rule in [plugin-api.md](plugin-api.md): a
default named `getStatusMessage`, `getActiveModelName` or `getToolGroups` could be taken for
an unrelated method a plugin already declares. `LlmBackend` and `ToolSource` gain no
members, and the ABI dump diff is additions only, so a plugin built against 26.40 loads
unchanged.

The registry and service defaults do nothing, and ai-core supplies the real behaviour. On an
ai-core built before this change, listeners are accepted and never called. Floor
`plugin.min_ide_version` at `26.41` to rely on the new members.

- **added — The AI prompt config engine and settings-pane helpers** _(ADFA-6281)_ **[verified]**
Every AI plugin carried its own copy of the code that reads and renders its prompt
config, and the credential screens their own copy of the reveal toggle and pane
Expand Down
6 changes: 3 additions & 3 deletions docs/plugin-api.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ The surface a plugin binds to is broader than one module. All of the following a
- Core: `IPlugin` (lifecycle), `PluginContext`, `PluginLogger`, `ServiceRegistry`, `ResourceManager`.
- Extension interfaces plugins **implement**: `UIExtension`, `EditorExtension`, `EditorTabExtension`, `DocumentationExtension`, `BuildActionExtension`, `SnippetExtension`, `ProjectExtension`, `FileOpenExtension`, `SettingsExtension`.
- IDE service interfaces plugins **call** (via `ServiceRegistry.get(X::class.java)`): `IdeProjectService`, `IdeEditorService`, `IdeFileService`, `IdeEnvironmentService`, `IdeArchiveService`, `IdeBuildService`, `IdeLogService`, `IdeUIService`, `IdeEditorTabService`, `IdeTooltipService`, `IdeThemeService`, `IdeFeatureFlagService`, `IdeCommandService`, `IdeTemplateService`, `IdeSnippetService`, `IdeSidebarService`.
- Cross-plugin service interfaces, where **one plugin implements what another calls** (via `SharedServices`): `LlmInferenceService` — implemented by ai-core, called by every AI plugin — together with the types nested in it that a *backend* plugin implements (`LlmBackend`, `HistoryCapableBackend`, `ToolCallingBackend`, `CancellableBackend`, `ConfigurableBackend`, `EmbeddingBackend`) and the value types either side constructs (`ChatMessage`, `LlmConfig`, `LlmResponse`, `SystemPromptRequest`, `ToolDefinition`, `ToolCallRequest`). Also `ToolSourceRegistry` — implemented by ai-core, called by any plugin contributing tools to the agent — with `ToolSource` and `ToolSpec`, which a *contributing* plugin implements, `ToolInvocation`, which ai-core constructs and passes to `ToolSource.invoke`, and `ToolOutcome`, which the source returns.
- Utility classes plugins **instantiate**: `KeystoreSecretStore` (AES/GCM over the Android Keystore, alias supplied by the caller); the AI prompt config engine in `ai.prompt` (`PromptTemplateEngine`, `PromptConfigLoader`, `PromptConfigStore`, `PromptConfigDocument`, `PromptConfigObject`, `AssetPromptConfigSource`), generic over a plugin's own config type through `PromptConfigParser` and `PromptConfigProvider`, which a plugin implements; and the settings-pane helpers in `ai.ui` (`SecretRevealController` with `RevealToggle`, `applyPaneStyling` with `PaneStyle`, `ButtonColors` and `FieldColors`). Host-side implementations rather than interfaces, so plugins share one copy in the process instead of compiling their own.
- Cross-plugin service interfaces, where **one plugin implements what another calls** (via `SharedServices`): `LlmInferenceService` — implemented by ai-core, called by every AI plugin — together with the types nested in it that a *backend* plugin implements (`LlmBackend`, `HistoryCapableBackend`, `ToolCallingBackend`, `CancellableBackend`, `ConfigurableBackend`, `EmbeddingBackend`, `EmbeddingModelSelectable`, `StatusReportingBackend`, `ActiveModelReportingBackend`), `BackendChangeListener`, which a *consumer* implements, and the value types either side constructs (`ChatMessage`, `LlmConfig`, `LlmResponse`, `SystemPromptRequest`, `ToolDefinition`, `ToolCallRequest`). Also `ToolSourceRegistry` — implemented by ai-core, called by any plugin contributing tools to the agent — with `ToolSource`, `StatusReportingToolSource`, `GroupedToolSource`, `ToolSpec` and `ToolGroup`, which a *contributing* plugin implements, `ToolSourceListener`, which a *consumer* implements, `ToolInvocation`, which ai-core constructs and passes to `ToolSource.invoke`, and `ToolOutcome`, which the source returns. Both contracts report health as the top-level `CapabilityStatus` enum.
- Utility classes plugins **instantiate**: `KeystoreSecretStore` (AES/GCM over the Android Keystore, alias supplied by the caller); the AI prompt config engine in `ai.prompt` (`PromptTemplateEngine`, `PromptConfigLoader`, `PromptConfigStore`, `PromptConfigDocument`, `PromptConfigObject`, `AssetPromptConfigSource`), generic over a plugin's own config type through `PromptConfigParser` and `PromptConfigProvider`, which a plugin implements; and the settings-pane helpers in `ai.ui` (`SecretRevealController` with `RevealToggle`, `applyPaneStyling` with `PaneStyle`, `ButtonColors` and `FieldColors`); and `ai.LlmBackendRegistration`, which keeps a backend plugin registered with AI Core's router. Host-side implementations rather than interfaces, so plugins share one copy in the process instead of compiling their own.
- Data classes plugins **construct** (e.g. `MenuItem`, `TabItem`, `EditorTabItem`, `NavigationItem`, `ToolbarAction`, `FabAction`, `PluginBuildAction`, `SnippetContribution`, `PluginTooltipEntry`, `PluginSettingsEntry`).
- Enums / sealed types plugins **reference**: `PluginPermission`, `ShowAsAction`, `ArchiveFormat`, `BuildActionCategory`, `ToolbarActionIds`, `CommandSpec`, `CommandResult`, `ExtractResult`, `KeystoreSecretStore.Stored`. Sealed, so a plugin `when`s over the cases exhaustively — adding one is a **breaking** change, not an additive one.
- **Wire/format contracts outside the module:**
Expand All @@ -40,7 +40,7 @@ These look source-compatible but break already-built `.cgp` plugins:
- **Interface methods — direction matters.** Ask who implements the interface before you apply a rule; the answer is not "host" just because the name ends in `Service`.
- *Extension interfaces* (`UIExtension`, `BuildActionExtension`, …) are implemented **by plugins**: adding a method is breaking for them (even a defaulted one can break depending on compilation). Provide defaults and prefer additive optional hooks.
- *Host service interfaces* (`Ide*Service`) are implemented **by the host** and only called by plugins: **adding** a method is safe; changing or removing a signature is breaking.
- *Plugin-implemented service interfaces* (`LlmInferenceService` and the backend interfaces nested in it) are implemented **by a plugin** even though they are shaped like services. The extension-interface rule applies, not the host-service one: **adding** a method is breaking. A Kotlin implementor's existing method loses its `override` when a Java `default` appears above it, so the break is a compile error in the *other* repo — which the impact check below is what catches. Prefer a new interface extending the old one over a new method on it.
- *Plugin-implemented service interfaces* (`LlmInferenceService` and the backend interfaces nested in it) are implemented **by a plugin** even though they are shaped like services. The extension-interface rule applies, not the host-service one: **adding** a method is breaking. A Kotlin implementor that already declares a same-signature method without `override` stops compiling when a Java `default` appears above it, so the break is in the *other* repo — the impact check below catches it. Prefer a new interface extending the old one over a new method on it: `StatusReportingBackend`, `ActiveModelReportingBackend` and `StatusReportingToolSource` follow this, and the consumer asks with `instanceof`. The same rule covers `ToolSource`, which every contributing plugin implements. `LlmInferenceService` and `ToolSourceRegistry` themselves took `default` methods in 26.41 as an exception: AI Core is their only implementor, and its released implementations were checked to declare none of the new names.
- **Enum constants.** Removing or renaming a constant (`PluginPermission`, `ShowAsAction`, `ArchiveFormat`, `ToolbarActionIds`, `BuildActionCategory`) breaks plugins that name it; adding one can still break an exhaustive `when`.
- **Types & nullability.** Flipping nullable↔non-null, changing a parameter/return type, or `val`↔`var` on an API property.
- **Moving or renaming** any class/package under `com.itsaky.androidide.plugins.*` — breaks imports and `ServiceRegistry.get(...)` lookups.
Expand Down
Loading
Loading