openclaw
Version:
Multi-channel AI gateway with extensible messaging integrations
101 lines (81 loc) • 3.63 kB
Markdown
summary: "The sequence, file checklist, and contract-test pattern for adding a capability to the plugin system"
read_when:
- Adding a new plugin capability
- You are deciding what core should own versus a vendor plugin
- You need the file checklist and contract-test pattern for a new capability
title: "Adding a new capability"
sidebarTitle: "New capability"
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](/plugins/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
`OpenClawPluginApi` 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 OpenClaw stays opinionated without becoming hardcoded to one
provider's worldview. See [Adding capabilities](/plugins/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:
- core contract types in `src/<capability>/types.ts`
- core runner/runtime helper in `src/<capability>/runtime.ts`
- plugin API registration surface in `src/plugins/types.ts`
- plugin registry wiring in `src/plugins/registry.ts`
- plugin runtime exposure in `src/plugins/runtime/*` when feature/channel
plugins need to consume it
- capture/test helpers in `src/test-utils/plugin-registration.ts`
- ownership/contract assertions in `src/plugins/contracts/registry.ts`
- operator/plugin docs in `docs/`
If one of those surfaces is missing, that is usually a sign the capability is
not fully integrated yet.
### Capability template
Minimal pattern:
```ts
// 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/plugins/contracts/registry.ts` exposes ownership
lookups such as `providerContractPluginIds`; tests assert a plugin's
`contracts.videoGenerationProviders` list matches what it actually registers):
```ts
expect(pluginManifest.contracts?.videoGenerationProviders).toEqual(["xai"]);
```
That keeps the rule simple:
- core owns the capability contract + orchestration
- vendor plugins own vendor implementations
- feature/channel plugins consume runtime helpers
- contract tests keep ownership explicit