The recommended sequence for adding a capability the plugin API does not have yet, plus the file checklist and contract-test pattern. Part of the Plugin architecture internals guide.

Adding a new capability

When a plugin needs behavior that does not fit the current API, do not bypass the plugin system with a private reach-in. Add the missing capability.

Recommended sequence:

  1. Define the core contract. Decide what shared behavior core should own: policy, fallback, config merge, lifecycle, channel-facing semantics, and runtime helper shape.
  2. Add typed plugin registration/runtime surfaces. Extend ApusPluginApi and/or api.runtime with the smallest useful typed capability surface.
  3. Wire core + channel/feature consumers. Channels and feature plugins should consume the new capability through core, not by importing a vendor implementation directly.
  4. Register vendor implementations. Vendor plugins then register their backends against the capability.
  5. Add contract coverage. Add tests so ownership and registration shape stay explicit over time.

This is how Apus stays opinionated without becoming hardcoded to one provider's worldview. See Adding capabilities for a concrete file checklist and worked example.

Capability checklist

When you add a new capability, the implementation should usually touch these surfaces together:

If one of those surfaces is missing, that is usually a sign the capability is not fully integrated yet.

Capability template

Minimal pattern:

// core contract
export type VideoGenerationProviderPlugin = {
  id: string;
  label: string;
  generateVideo: (req: VideoGenerationRequest) => Promise<VideoGenerationResult>;
};

// plugin API
api.registerVideoGenerationProvider({
  id: "xai",
  label: "xAI",
  async generateVideo(req) {
    // generateXaiVideo is a placeholder for your own vendor call.
    return await generateXaiVideo(req);
  },
});

// shared runtime helper for feature/channel plugins
const clip = await api.runtime.videoGeneration.generate({
  prompt: "Show the robot walking through the lab.",
  cfg,
});

Contract test pattern (src/extensibility/plugins/contracts/registry.ts exposes ownership lookups such as providerContractPluginIds; tests assert a plugin's contracts.videoGenerationProviders list matches what it actually registers):

expect(pluginManifest.contracts?.videoGenerationProviders).toEqual(["xai"]);

That keeps the rule simple: