docs(plugin-sdk): document public SDK surface

This commit is contained in:
Peter Steinberger
2026-03-22 08:50:43 -07:00
parent e1ff24903f
commit 05279539a8
34 changed files with 1260 additions and 16 deletions

View File

@@ -238,5 +238,65 @@
{
"source": "env var",
"target": "环境变量"
},
{
"source": "Plugin SDK",
"target": "插件 SDK"
},
{
"source": "Plugin SDK Overview",
"target": "插件 SDK 概览"
},
{
"source": "SDK Overview",
"target": "SDK 概览"
},
{
"source": "Plugin Entry Points",
"target": "插件入口点"
},
{
"source": "Entry Points",
"target": "入口点"
},
{
"source": "Plugin Runtime",
"target": "插件运行时"
},
{
"source": "Runtime",
"target": "运行时"
},
{
"source": "Plugin Setup",
"target": "插件设置"
},
{
"source": "Setup",
"target": "设置"
},
{
"source": "Channel Plugin SDK",
"target": "渠道插件 SDK"
},
{
"source": "Channel Plugins",
"target": "渠道插件"
},
{
"source": "Provider Plugin SDK",
"target": "提供商插件 SDK"
},
{
"source": "Provider Plugins",
"target": "提供商插件"
},
{
"source": "Plugin SDK Testing",
"target": "插件 SDK 测试"
},
{
"source": "Testing",
"target": "测试"
}
]

View File

@@ -1045,6 +1045,18 @@
"plugins/architecture"
]
},
{
"group": "Plugin SDK",
"pages": [
"plugins/sdk-overview",
"plugins/sdk-entrypoints",
"plugins/sdk-runtime",
"plugins/sdk-setup",
"plugins/sdk-channel-plugins",
"plugins/sdk-provider-plugins",
"plugins/sdk-testing"
]
},
{
"group": "Skills",
"pages": [

View File

@@ -181,7 +181,7 @@ my-plugin/
// Correct: focused subpaths
import { definePluginEntry } from "openclaw/plugin-sdk/plugin-entry";
import { createPluginRuntimeStore } from "openclaw/plugin-sdk/runtime-store";
import { buildOauthProviderAuthResult } from "openclaw/plugin-sdk/provider-oauth";
import { buildOauthProviderAuthResult } from "openclaw/plugin-sdk/provider-auth";
// Wrong: monolithic root (lint will reject this)
import { ... } from "openclaw/plugin-sdk";
@@ -195,22 +195,23 @@ my-plugin/
| --- | --- |
| `plugin-sdk/plugin-entry` | Canonical `definePluginEntry` helper + provider/plugin entry types |
| `plugin-sdk/core` | Channel entry helpers, channel builders, and shared base types |
| `plugin-sdk/channel-setup` | Setup wizard adapters |
| `plugin-sdk/runtime-store` | Safe module-level runtime storage |
| `plugin-sdk/setup` | Shared setup-wizard helpers |
| `plugin-sdk/channel-setup` | Channel setup adapters |
| `plugin-sdk/channel-pairing` | DM pairing primitives |
| `plugin-sdk/channel-reply-pipeline` | Reply prefix + typing wiring |
| `plugin-sdk/channel-config-schema` | Config schema builders |
| `plugin-sdk/channel-policy` | Group/DM policy helpers |
| `plugin-sdk/channel-actions` | Shared `message` tool schema helpers |
| `plugin-sdk/channel-contract` | Pure channel types |
| `plugin-sdk/secret-input` | Secret input parsing/helpers |
| `plugin-sdk/webhook-ingress` | Webhook request/target helpers |
| `plugin-sdk/runtime-store` | Persistent plugin storage |
| `plugin-sdk/allow-from` | Allowlist resolution |
| `plugin-sdk/reply-payload` | Message reply types |
| `plugin-sdk/provider-oauth` | OAuth login + PKCE helpers |
| `plugin-sdk/provider-auth` | Provider auth and OAuth helpers |
| `plugin-sdk/provider-onboard` | Provider onboarding config patches |
| `plugin-sdk/provider-models` | Model catalog helpers |
| `plugin-sdk/testing` | Test utilities |
</Accordion>
Use the narrowest subpath that matches the job.
Use the narrowest subpath that matches the job. For the curated map and
examples, see [Plugin SDK Overview](/plugins/sdk-overview).
</Step>
@@ -266,7 +267,7 @@ my-plugin/
For unit tests, import test helpers from the testing surface:
```typescript
import { createTestRuntime } from "openclaw/plugin-sdk/testing";
import { createWindowsCmdShimFixture } from "openclaw/plugin-sdk/testing";
```
</Step>
@@ -370,6 +371,13 @@ patterns is strongly recommended.
## Related
- [Plugin SDK Migration](/plugins/sdk-migration) — migrating from deprecated compat surfaces
- [Plugin SDK Overview](/plugins/sdk-overview) — public SDK map and subpath guidance
- [Plugin Entry Points](/plugins/sdk-entrypoints) — `definePluginEntry` and `defineChannelPluginEntry`
- [Plugin Runtime](/plugins/sdk-runtime) — injected runtime and runtime-store
- [Plugin Setup](/plugins/sdk-setup) — setup, channel setup, and secret input helpers
- [Channel Plugin SDK](/plugins/sdk-channel-plugins) — channel contracts and actions
- [Provider Plugin SDK](/plugins/sdk-provider-plugins) — provider auth, onboarding, and catalogs
- [Plugin SDK Testing](/plugins/sdk-testing) — public test helpers
- [Plugin Architecture](/plugins/architecture) — internals and capability model
- [Plugin Manifest](/plugins/manifest) — full manifest schema
- [Plugin Agent Tools](/plugins/building-plugins#registering-agent-tools) — adding agent tools in a plugin

View File

@@ -0,0 +1,161 @@
---
title: "Channel Plugin SDK"
sidebarTitle: "Channel Plugins"
summary: "Contracts and helpers for native messaging channel plugins, including actions, routing, pairing, and setup"
read_when:
- You are building a native channel plugin
- You need to implement the shared `message` tool for a channel
- You need pairing, setup, or routing helpers for a channel
---
# Channel Plugin SDK
Channel plugins use `defineChannelPluginEntry(...)` from
`openclaw/plugin-sdk/core` and implement the `ChannelPlugin` contract.
## Minimal channel entry
```ts
import { defineChannelPluginEntry } from "openclaw/plugin-sdk/core";
import { exampleChannelPlugin } from "./src/channel.js";
import { setExampleRuntime } from "./src/runtime.js";
export default defineChannelPluginEntry({
id: "example-channel",
name: "Example Channel",
description: "Example native channel plugin",
plugin: exampleChannelPlugin,
setRuntime: setExampleRuntime,
});
```
## `ChannelPlugin` shape
Important sections of the contract:
- `meta`: docs, labels, and picker metadata
- `capabilities`: replies, polls, reactions, threads, media, and chat types
- `config` and `configSchema`: account resolution and config parsing
- `setup` and `setupWizard`: onboarding/setup flow
- `security`: DM policy and allowlist behavior
- `messaging`: target parsing and outbound session routing
- `actions`: shared `message` tool discovery and execution
- `pairing`, `threading`, `status`, `lifecycle`, `groups`, `directory`
For pure types, import from `openclaw/plugin-sdk/channel-contract`.
## Shared `message` tool
Channel plugins own their channel-specific part of the shared `message` tool
through `ChannelMessageActionAdapter`.
```ts
import { Type } from "@sinclair/typebox";
import { createMessageToolButtonsSchema } from "openclaw/plugin-sdk/channel-actions";
export const exampleActions = {
describeMessageTool() {
return {
actions: ["send", "edit"],
capabilities: ["buttons"],
schema: {
visibility: "current-channel",
properties: {
buttons: createMessageToolButtonsSchema(),
threadId: Type.String(),
},
},
};
},
async handleAction(ctx) {
if (ctx.action === "send") {
return {
content: [{ type: "text", text: `send to ${String(ctx.params.to)}` }],
};
}
return {
content: [{ type: "text", text: `unsupported action: ${ctx.action}` }],
};
},
};
```
Key types:
- `ChannelMessageActionAdapter`
- `ChannelMessageActionContext`
- `ChannelMessageActionDiscoveryContext`
- `ChannelMessageToolDiscovery`
## Outbound routing helpers
When a channel plugin needs custom outbound routing, implement
`messaging.resolveOutboundSessionRoute(...)`.
Use `buildChannelOutboundSessionRoute(...)` from `plugin-sdk/core` to return the
standard route payload:
```ts
import { buildChannelOutboundSessionRoute } from "openclaw/plugin-sdk/core";
const messaging = {
resolveOutboundSessionRoute({ cfg, agentId, accountId, target }) {
return buildChannelOutboundSessionRoute({
cfg,
agentId,
channel: "example-channel",
accountId,
peer: { kind: "direct", id: target },
chatType: "direct",
from: accountId ?? "default",
to: target,
});
},
};
```
## Pairing helpers
Use `plugin-sdk/channel-pairing` for DM approval flows:
```ts
import { createChannelPairingController } from "openclaw/plugin-sdk/channel-pairing";
const pairing = createChannelPairingController({
core: runtime,
channel: "example-channel",
accountId: "default",
});
const result = pairing.issueChallenge({
agentId: "assistant",
requesterId: "user-123",
});
```
That surface also gives you scoped access to pairing storage helpers such as
allowlist reads and request upserts.
## Channel setup helpers
Use:
- `plugin-sdk/channel-setup` for optional or installable channels
- `plugin-sdk/setup` for setup adapters, DM policy, and allowlist prompts
- `plugin-sdk/webhook-ingress` for plugin-owned webhook routes
## Channel plugin guidance
- Keep transport-specific execution inside the channel package.
- Use `channel-contract` types in tests and local helpers.
- Keep `describeMessageTool(...)` and `handleAction(...)` aligned.
- Keep session routing in `messaging`, not in ad-hoc command handlers.
- Prefer focused subpaths over broad runtime coupling.
## Related
- [Plugin SDK Overview](/plugins/sdk-overview)
- [Plugin Entry Points](/plugins/sdk-entrypoints)
- [Plugin Setup](/plugins/sdk-setup)
- [Plugin Internals](/plugins/architecture)

View File

@@ -0,0 +1,159 @@
---
title: "Plugin Entry Points"
sidebarTitle: "Entry Points"
summary: "How to define plugin entry files for provider, tool, channel, and setup plugins"
read_when:
- You are writing a plugin `index.ts`
- You need to choose between `definePluginEntry` and `defineChannelPluginEntry`
- You are adding a separate `setup-entry.ts`
---
# Plugin Entry Points
OpenClaw has two main entry helpers:
- `definePluginEntry(...)` for general plugins
- `defineChannelPluginEntry(...)` for native messaging channels
There is also `defineSetupPluginEntry(...)` for a separate setup-only module.
## `definePluginEntry(...)`
Use this for providers, tools, commands, services, memory plugins, and context
engines.
```ts
import { definePluginEntry, type OpenClawPluginApi } from "openclaw/plugin-sdk/plugin-entry";
export default definePluginEntry({
id: "example-tools",
name: "Example Tools",
description: "Adds a command and a tool",
register(api: OpenClawPluginApi) {
api.registerCommand({
name: "example",
description: "Show plugin status",
handler: async () => ({ text: "example ok" }),
});
api.registerTool({
name: "example_lookup",
description: "Look up Example data",
parameters: {
type: "object",
properties: {
query: { type: "string" },
},
required: ["query"],
},
async execute(_callId, params) {
return {
content: [{ type: "text", text: `lookup: ${String(params.query)}` }],
};
},
});
},
});
```
## `defineChannelPluginEntry(...)`
Use this for a plugin that registers a `ChannelPlugin`.
```ts
import { defineChannelPluginEntry } from "openclaw/plugin-sdk/core";
import { channelPlugin } from "./src/channel.js";
import { setRuntime } from "./src/runtime.js";
export default defineChannelPluginEntry({
id: "example-channel",
name: "Example Channel",
description: "Example messaging plugin",
plugin: channelPlugin,
setRuntime,
registerFull(api) {
api.registerTool({
name: "example_channel_status",
description: "Inspect Example Channel state",
parameters: { type: "object", properties: {} },
async execute() {
return { content: [{ type: "text", text: "ok" }] };
},
});
},
});
```
### Why `registerFull(...)` exists
OpenClaw can load plugins in setup-focused registration modes. `registerFull`
lets a channel plugin skip extra runtime-only registrations such as tools while
still registering the channel capability itself.
Use it for:
- agent tools
- gateway-only routes
- runtime-only commands
Do not use it for the actual `ChannelPlugin`; that belongs in `plugin: ...`.
## `defineSetupPluginEntry(...)`
Use this when a channel ships a second module for setup flows.
```ts
import { defineSetupPluginEntry } from "openclaw/plugin-sdk/core";
import { exampleSetupPlugin } from "./src/channel.setup.js";
export default defineSetupPluginEntry(exampleSetupPlugin);
```
This keeps the setup entry shape explicit and matches the bundled channel
pattern used in OpenClaw.
## One plugin, many capabilities
A single entry file can register multiple capabilities:
```ts
import { definePluginEntry, type OpenClawPluginApi } from "openclaw/plugin-sdk/plugin-entry";
export default definePluginEntry({
id: "example-hybrid",
name: "Example Hybrid",
description: "Provider plus tools",
register(api: OpenClawPluginApi) {
api.registerProvider({
id: "example",
label: "Example",
auth: [],
});
api.registerTool({
name: "example_ping",
description: "Simple health check",
parameters: { type: "object", properties: {} },
async execute() {
return { content: [{ type: "text", text: "pong" }] };
},
});
},
});
```
## Entry-file checklist
- Give the plugin a stable `id`.
- Keep `name` and `description` human-readable.
- Put schema at the entry level when the plugin has config.
- Register only public capabilities inside `register(api)`.
- Keep channel plugins on `plugin-sdk/core`.
- Keep non-channel plugins on `plugin-sdk/plugin-entry`.
## Related
- [Plugin SDK Overview](/plugins/sdk-overview)
- [Plugin Runtime](/plugins/sdk-runtime)
- [Channel Plugin SDK](/plugins/sdk-channel-plugins)
- [Provider Plugin SDK](/plugins/sdk-provider-plugins)

View File

@@ -165,5 +165,6 @@ This is a temporary escape hatch, not a permanent solution.
## Related
- [Building Plugins](/plugins/building-plugins)
- [Plugin SDK Overview](/plugins/sdk-overview)
- [Plugin Internals](/plugins/architecture)
- [Plugin Manifest](/plugins/manifest)

View File

@@ -0,0 +1,175 @@
---
title: "Plugin SDK Overview"
sidebarTitle: "SDK Overview"
summary: "How the OpenClaw plugin SDK is organized, which subpaths are stable, and how to choose the right import"
read_when:
- You are starting a new OpenClaw plugin
- You need to choose the right plugin-sdk subpath
- You are replacing deprecated compat imports
---
# Plugin SDK Overview
The OpenClaw plugin SDK is split into **small public subpaths** under
`openclaw/plugin-sdk/<subpath>`.
Use the narrowest import that matches the job. That keeps plugin dependencies
small, avoids circular imports, and makes it clear which contract you depend on.
## Rules first
- Use focused imports such as `openclaw/plugin-sdk/plugin-entry`.
- Do not import the root `openclaw/plugin-sdk` barrel in new code.
- Do not import `openclaw/extension-api` in new code.
- Do not import `src/**` from plugin packages.
- Inside a plugin package, route internal imports through local files such as
`./api.ts` or `./runtime-api.ts`, not through the published SDK path for that
same plugin.
## SDK map
| Job | Subpath | Next page |
| ---------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------- |
| Define plugin entry modules | `plugin-sdk/plugin-entry`, `plugin-sdk/core` | [Plugin Entry Points](/plugins/sdk-entrypoints) |
| Use injected runtime helpers | `plugin-sdk/runtime`, `plugin-sdk/runtime-store` | [Plugin Runtime](/plugins/sdk-runtime) |
| Build setup/configure flows | `plugin-sdk/setup`, `plugin-sdk/channel-setup`, `plugin-sdk/secret-input` | [Plugin Setup](/plugins/sdk-setup) |
| Build channel plugins | `plugin-sdk/core`, `plugin-sdk/channel-contract`, `plugin-sdk/channel-actions`, `plugin-sdk/channel-pairing` | [Channel Plugin SDK](/plugins/sdk-channel-plugins) |
| Build provider plugins | `plugin-sdk/plugin-entry`, `plugin-sdk/provider-auth`, `plugin-sdk/provider-onboard`, `plugin-sdk/provider-models`, `plugin-sdk/provider-usage` | [Provider Plugin SDK](/plugins/sdk-provider-plugins) |
| Test plugin code | `plugin-sdk/testing` | [Plugin SDK Testing](/plugins/sdk-testing) |
## Typical plugin layout
```text
my-plugin/
├── package.json
├── openclaw.plugin.json
├── index.ts
├── setup-entry.ts
├── api.ts
├── runtime-api.ts
└── src/
├── provider.ts
├── setup.ts
└── provider.test.ts
```
```ts
// api.ts
export {
definePluginEntry,
type OpenClawPluginApi,
type ProviderAuthContext,
type ProviderAuthResult,
} from "openclaw/plugin-sdk/plugin-entry";
```
## What belongs where
### Entry helpers
- `plugin-sdk/plugin-entry` is the default entry surface for providers, tools,
commands, services, memory plugins, and context engines.
- `plugin-sdk/core` adds channel-focused helpers such as
`defineChannelPluginEntry(...)`.
### Runtime helpers
- Use `api.runtime.*` for trusted in-process helpers that OpenClaw injects at
registration time.
- Use `plugin-sdk/runtime-store` when plugin modules need a mutable runtime slot
that is initialized later.
### Setup helpers
- `plugin-sdk/setup` contains shared setup-wizard helpers and config patch
helpers.
- `plugin-sdk/channel-setup` contains channel-specific setup adapters.
- `plugin-sdk/secret-input` exposes the shared secret-input schema helpers.
### Channel helpers
- `plugin-sdk/channel-contract` exports pure channel types.
- `plugin-sdk/channel-actions` covers shared `message` tool schema helpers.
- `plugin-sdk/channel-pairing` covers pairing approval flows.
- `plugin-sdk/webhook-ingress` covers plugin-owned webhook routes.
### Provider helpers
- `plugin-sdk/provider-auth` covers auth flows and credential helpers.
- `plugin-sdk/provider-onboard` covers config patches after auth/setup.
- `plugin-sdk/provider-models` covers catalog and model-definition helpers.
- `plugin-sdk/provider-usage` covers usage snapshot helpers.
- `plugin-sdk/provider-setup` and `plugin-sdk/self-hosted-provider-setup`
cover self-hosted and local-model onboarding.
## Example: mixing subpaths in one plugin
```ts
import { definePluginEntry, type OpenClawPluginApi } from "openclaw/plugin-sdk/plugin-entry";
import { createProviderApiKeyAuthMethod } from "openclaw/plugin-sdk/provider-auth";
import { applyProviderConfigWithDefaultModel } from "openclaw/plugin-sdk/provider-onboard";
import { buildSecretInputSchema } from "openclaw/plugin-sdk/secret-input";
export default definePluginEntry({
id: "example-provider",
name: "Example Provider",
description: "Small provider plugin example",
configSchema: {
jsonSchema: {
type: "object",
additionalProperties: false,
properties: {
apiKey: { type: "string" },
},
},
safeParse(value) {
return buildSecretInputSchema().safeParse((value as { apiKey?: unknown })?.apiKey);
},
},
register(api: OpenClawPluginApi) {
api.registerProvider({
id: "example",
label: "Example",
auth: [
createProviderApiKeyAuthMethod({
providerId: "example",
methodId: "api-key",
label: "Example API key",
optionKey: "exampleApiKey",
flagName: "--example-api-key",
envVar: "EXAMPLE_API_KEY",
promptMessage: "Enter Example API key",
profileId: "example:default",
defaultModel: "example/default",
applyConfig: (cfg) =>
applyProviderConfigWithDefaultModel(cfg, "example", {
id: "default",
name: "Default",
}),
}),
],
});
},
});
```
## Choose the smallest public seam
If a helper exists on a focused subpath, prefer that over a broader runtime
surface.
- Prefer `plugin-sdk/provider-auth` over reaching into unrelated provider files.
- Prefer `plugin-sdk/channel-contract` for types in tests and helper modules.
- Prefer `plugin-sdk/runtime-store` over custom mutable globals.
- Prefer `plugin-sdk/testing` for shared test fixtures.
## Related
- [Building Plugins](/plugins/building-plugins)
- [Plugin Entry Points](/plugins/sdk-entrypoints)
- [Plugin Runtime](/plugins/sdk-runtime)
- [Plugin Setup](/plugins/sdk-setup)
- [Channel Plugin SDK](/plugins/sdk-channel-plugins)
- [Provider Plugin SDK](/plugins/sdk-provider-plugins)
- [Plugin SDK Testing](/plugins/sdk-testing)
- [Plugin SDK Migration](/plugins/sdk-migration)

View File

@@ -0,0 +1,184 @@
---
title: "Provider Plugin SDK"
sidebarTitle: "Provider Plugins"
summary: "Contracts and helper subpaths for model-provider plugins, including auth, onboarding, catalogs, and usage"
read_when:
- You are building a model provider plugin
- You need auth helpers for API keys or OAuth
- You need onboarding config patches or catalog helpers
---
# Provider Plugin SDK
Provider plugins use `definePluginEntry(...)` and call `api.registerProvider(...)`
with a `ProviderPlugin` definition.
## Minimal provider entry
```ts
import { definePluginEntry, type OpenClawPluginApi } from "openclaw/plugin-sdk/plugin-entry";
export default definePluginEntry({
id: "example-provider",
name: "Example Provider",
description: "Example text-inference provider plugin",
register(api: OpenClawPluginApi) {
api.registerProvider({
id: "example",
label: "Example",
auth: [],
});
},
});
```
## Provider subpaths
| Subpath | Use it for |
| --------------------------------------- | ---------------------------------------------- |
| `plugin-sdk/provider-auth` | API key, OAuth, auth-profile, and PKCE helpers |
| `plugin-sdk/provider-onboard` | Config patches after setup/auth |
| `plugin-sdk/provider-models` | Model-definition and catalog helpers |
| `plugin-sdk/provider-setup` | Shared local/self-hosted setup flows |
| `plugin-sdk/self-hosted-provider-setup` | OpenAI-compatible self-hosted providers |
| `plugin-sdk/provider-usage` | Usage snapshot fetch helpers |
## API key auth
`createProviderApiKeyAuthMethod(...)` is the standard helper for API-key
providers:
```ts
import { createProviderApiKeyAuthMethod } from "openclaw/plugin-sdk/provider-auth";
import { applyProviderConfigWithDefaultModel } from "openclaw/plugin-sdk/provider-onboard";
const auth = [
createProviderApiKeyAuthMethod({
providerId: "example",
methodId: "api-key",
label: "Example API key",
optionKey: "exampleApiKey",
flagName: "--example-api-key",
envVar: "EXAMPLE_API_KEY",
promptMessage: "Enter Example API key",
profileId: "example:default",
defaultModel: "example/default",
applyConfig: (cfg) =>
applyProviderConfigWithDefaultModel(cfg, "example", {
id: "default",
name: "Default",
}),
}),
];
```
## OAuth auth
`buildOauthProviderAuthResult(...)` builds the standard auth result payload for
OAuth-style providers:
```ts
import { buildOauthProviderAuthResult } from "openclaw/plugin-sdk/provider-auth";
async function runOAuthLogin() {
return buildOauthProviderAuthResult({
providerId: "example-portal",
defaultModel: "example-portal/default",
access: "access-token",
refresh: "refresh-token",
email: "user@example.com",
notes: ["Tokens auto-refresh when the provider supports refresh tokens."],
});
}
```
## Catalog and discovery hooks
Provider plugins usually implement either `catalog` or the legacy `discovery`
alias. `catalog` is preferred.
```ts
api.registerProvider({
id: "example",
label: "Example",
auth,
catalog: {
order: "simple",
async run(ctx) {
const apiKey = ctx.resolveProviderApiKey("example").apiKey;
if (!apiKey) {
return null;
}
return {
provider: {
api: "openai",
baseUrl: "https://api.example.com/v1",
apiKey,
models: [
{
id: "default",
name: "Default",
input: ["text"],
},
],
},
};
},
},
});
```
## Onboarding config patches
`plugin-sdk/provider-onboard` keeps post-auth config writes consistent.
Common helpers:
- `applyProviderConfigWithDefaultModel(...)`
- `applyProviderConfigWithDefaultModels(...)`
- `applyProviderConfigWithModelCatalog(...)`
- `applyAgentDefaultModelPrimary(...)`
- `ensureModelAllowlistEntry(...)`
## Self-hosted and local model setup
Use `plugin-sdk/provider-setup` or
`plugin-sdk/self-hosted-provider-setup` when the provider is an OpenAI-style
backend, Ollama, SGLang, or vLLM.
Examples from the shared setup surfaces:
- `promptAndConfigureOllama(...)`
- `configureOllamaNonInteractive(...)`
- `promptAndConfigureOpenAICompatibleSelfHostedProvider(...)`
- `discoverOpenAICompatibleSelfHostedProvider(...)`
These helpers keep setup behavior aligned with built-in provider flows.
## Usage snapshots
If the provider owns quota or usage endpoints, use `resolveUsageAuth(...)` and
`fetchUsageSnapshot(...)`.
`plugin-sdk/provider-usage` includes shared fetch helpers such as:
- `fetchClaudeUsage(...)`
- `fetchCodexUsage(...)`
- `fetchGeminiUsage(...)`
- `fetchMinimaxUsage(...)`
- `fetchZaiUsage(...)`
## Provider guidance
- Keep auth logic in `provider-auth`.
- Keep config mutation in `provider-onboard`.
- Keep catalog/model helpers in `provider-models`.
- Keep usage logic in `provider-usage`.
- Use `catalog`, not `discovery`, in new plugins.
## Related
- [Plugin SDK Overview](/plugins/sdk-overview)
- [Plugin Entry Points](/plugins/sdk-entrypoints)
- [Plugin Setup](/plugins/sdk-setup)
- [Plugin Internals](/plugins/architecture#provider-runtime-hooks)

156
docs/plugins/sdk-runtime.md Normal file
View File

@@ -0,0 +1,156 @@
---
title: "Plugin Runtime"
sidebarTitle: "Runtime"
summary: "How `api.runtime` works, when to use it, and how to manage plugin runtime state safely"
read_when:
- You need to call runtime helpers from a plugin
- You are deciding between hooks and injected runtime
- You need a safe module-level runtime store
---
# Plugin Runtime
Native OpenClaw plugins receive a trusted runtime through `api.runtime`.
Use it for **host-owned operations** that should stay inside OpenClaws runtime:
- reading and writing config
- agent/session helpers
- system commands with OpenClaw timeouts
- media, speech, image-generation, and web-search runtime calls
- channel-owned helpers for bundled channel plugins
## When to use runtime vs focused SDK helpers
- Use focused SDK helpers when a public subpath already models the job.
- Use `api.runtime.*` when the host owns the operation or state.
- Prefer hooks for loose integrations that do not need tight in-process access.
## Runtime namespaces
| Namespace | What it covers |
| -------------------------------- | -------------------------------------------------- |
| `api.runtime.config` | Load and persist OpenClaw config |
| `api.runtime.agent` | Agent workspace, identity, timeouts, session store |
| `api.runtime.system` | System events, heartbeats, command execution |
| `api.runtime.media` | File/media loading and transforms |
| `api.runtime.tts` | Speech synthesis and voice listing |
| `api.runtime.mediaUnderstanding` | Image/audio/video understanding |
| `api.runtime.imageGeneration` | Image generation providers |
| `api.runtime.webSearch` | Runtime web-search execution |
| `api.runtime.modelAuth` | Resolve model/provider credentials |
| `api.runtime.subagent` | Spawn, wait, inspect, and delete subagent sessions |
| `api.runtime.channel` | Channel-heavy helpers for native channel plugins |
## Example: read and persist config
```ts
import { definePluginEntry, type OpenClawPluginApi } from "openclaw/plugin-sdk/plugin-entry";
export default definePluginEntry({
id: "talk-settings",
name: "Talk Settings",
description: "Example runtime config write",
register(api: OpenClawPluginApi) {
api.registerCommand({
name: "talk-mode",
description: "Enable talk mode",
handler: async () => {
const cfg = api.runtime.config.loadConfig();
const nextConfig = {
...cfg,
talk: {
...cfg.talk,
enabled: true,
},
};
await api.runtime.config.writeConfigFile(nextConfig);
return { text: "talk mode enabled" };
},
});
},
});
```
## Example: use a runtime service owned by OpenClaw
```ts
const cfg = api.runtime.config.loadConfig();
const voices = await api.runtime.tts.listVoices({
provider: "openai",
cfg,
});
return {
text: voices.map((voice) => `${voice.name ?? voice.id}: ${voice.id}`).join("\n"),
};
```
## `createPluginRuntimeStore(...)`
Plugin modules often need a small mutable slot for runtime-backed helpers. Use
`plugin-sdk/runtime-store` instead of an unguarded `let runtime`.
```ts
import { defineChannelPluginEntry } from "openclaw/plugin-sdk/core";
import { createPluginRuntimeStore } from "openclaw/plugin-sdk/runtime-store";
import { channelPlugin } from "./src/channel.js";
const runtimeStore = createPluginRuntimeStore<{
logger: { info(message: string): void };
}>("Example Channel runtime not initialized");
export function setExampleRuntime(runtime: { logger: { info(message: string): void } }) {
runtimeStore.setRuntime(runtime);
}
export function getExampleRuntime() {
return runtimeStore.getRuntime();
}
export default defineChannelPluginEntry({
id: "example-channel",
name: "Example Channel",
description: "Example runtime store usage",
plugin: channelPlugin,
setRuntime: setExampleRuntime,
});
```
`createPluginRuntimeStore(...)` gives you:
- `setRuntime(next)`
- `clearRuntime()`
- `tryGetRuntime()`
- `getRuntime()`
`getRuntime()` throws with your custom message if the runtime was never set.
## Channel runtime note
`api.runtime.channel.*` is the heaviest namespace. It exists for native channel
plugins that need tight coupling with the OpenClaw messaging stack.
Prefer narrower subpaths such as:
- `plugin-sdk/channel-pairing`
- `plugin-sdk/channel-actions`
- `plugin-sdk/channel-feedback`
- `plugin-sdk/channel-lifecycle`
Use `api.runtime.channel.*` when the operation is clearly host-owned and there
is no smaller public seam.
## Runtime safety guidelines
- Do not cache config snapshots longer than needed.
- Prefer `createPluginRuntimeStore(...)` for shared module state.
- Keep runtime-backed code behind small local helpers.
- Avoid reaching into runtime namespaces you do not need.
## Related
- [Plugin SDK Overview](/plugins/sdk-overview)
- [Plugin Entry Points](/plugins/sdk-entrypoints)
- [Plugin Setup](/plugins/sdk-setup)
- [Channel Plugin SDK](/plugins/sdk-channel-plugins)

132
docs/plugins/sdk-setup.md Normal file
View File

@@ -0,0 +1,132 @@
---
title: "Plugin Setup"
sidebarTitle: "Setup"
summary: "Shared setup-wizard helpers for channel plugins, provider plugins, and secret inputs"
read_when:
- You are building a setup or onboarding flow
- You need shared allowlist or DM policy setup helpers
- You need the shared secret-input schema
---
# Plugin Setup
OpenClaw exposes shared setup helpers so plugin setup flows behave like the
built-in ones.
Main subpaths:
- `openclaw/plugin-sdk/setup`
- `openclaw/plugin-sdk/channel-setup`
- `openclaw/plugin-sdk/secret-input`
## Channel setup helpers
Use `plugin-sdk/channel-setup` when a channel plugin needs the standard setup
adapter and setup wizard shapes.
### Optional channel plugins
If a channel is installable but not always present, use
`createOptionalChannelSetupSurface(...)`:
```ts
import { createOptionalChannelSetupSurface } from "openclaw/plugin-sdk/channel-setup";
export const optionalExampleSetup = createOptionalChannelSetupSurface({
channel: "example",
label: "Example Channel",
npmSpec: "@openclaw/example-channel",
docsPath: "/channels/example",
});
```
That returns:
- `setupAdapter`
- `setupWizard`
Both surfaces produce a consistent “install this plugin first” experience.
## Shared setup helpers
`plugin-sdk/setup` re-exports the setup primitives used by bundled channels.
Common helpers:
- `applySetupAccountConfigPatch(...)`
- `createPatchedAccountSetupAdapter(...)`
- `createEnvPatchedAccountSetupAdapter(...)`
- `createTopLevelChannelDmPolicy(...)`
- `setSetupChannelEnabled(...)`
- `promptResolvedAllowFrom(...)`
- `promptSingleChannelSecretInput(...)`
### Example: patch channel config in setup
```ts
import {
DEFAULT_ACCOUNT_ID,
createPatchedAccountSetupAdapter,
setSetupChannelEnabled,
} from "openclaw/plugin-sdk/setup";
export const exampleSetupAdapter = createPatchedAccountSetupAdapter({
resolveAccountId: ({ accountId }) => accountId ?? DEFAULT_ACCOUNT_ID,
applyPatch: ({ nextConfig, accountId }) => {
const resolvedAccountId = accountId ?? DEFAULT_ACCOUNT_ID;
return setSetupChannelEnabled({
nextConfig,
channel: "example",
accountId: resolvedAccountId,
enabled: true,
});
},
});
```
## Secret input schema
Use `plugin-sdk/secret-input` instead of rolling your own secret-input parser.
```ts
import {
buildOptionalSecretInputSchema,
buildSecretInputArraySchema,
buildSecretInputSchema,
hasConfiguredSecretInput,
} from "openclaw/plugin-sdk/secret-input";
const ApiKeySchema = buildSecretInputSchema();
const OptionalApiKeySchema = buildOptionalSecretInputSchema();
const ExtraKeysSchema = buildSecretInputArraySchema();
const parsed = OptionalApiKeySchema.safeParse(process.env.EXAMPLE_API_KEY);
if (parsed.success && hasConfiguredSecretInput(parsed.data)) {
// ...
}
```
## Provider setup note
Provider-specific onboarding helpers live on provider-focused subpaths:
- `plugin-sdk/provider-auth`
- `plugin-sdk/provider-onboard`
- `plugin-sdk/provider-setup`
- `plugin-sdk/self-hosted-provider-setup`
See [Provider Plugin SDK](/plugins/sdk-provider-plugins).
## Setup guidance
- Keep setup input schemas strict and small.
- Reuse OpenClaws allowlist, DM-policy, and secret-input helpers.
- Keep setup-entry modules thin; move behavior into `src/`.
- Link docs from setup flows when install or auth steps are manual.
## Related
- [Plugin SDK Overview](/plugins/sdk-overview)
- [Plugin Entry Points](/plugins/sdk-entrypoints)
- [Provider Plugin SDK](/plugins/sdk-provider-plugins)
- [Plugin Manifest](/plugins/manifest)

112
docs/plugins/sdk-testing.md Normal file
View File

@@ -0,0 +1,112 @@
---
title: "Plugin SDK Testing"
sidebarTitle: "Testing"
summary: "How to test plugin code with the public testing helpers and small local test doubles"
read_when:
- You are writing tests for a plugin
- You need fixtures for Windows command shims or shared routing failures
- You want to know what the public testing surface includes
---
# Plugin SDK Testing
OpenClaw keeps the public testing surface intentionally small.
Use `openclaw/plugin-sdk/testing` for helpers that are stable enough to support
for plugin authors, and build small plugin-local doubles for everything else.
## Public testing helpers
Current helpers include:
- `createWindowsCmdShimFixture(...)`
- `installCommonResolveTargetErrorCases(...)`
- `shouldAckReaction(...)`
- `removeAckReactionAfterReply(...)`
The testing surface also re-exports some shared types:
- `OpenClawConfig`
- `PluginRuntime`
- `RuntimeEnv`
- `ChannelAccountSnapshot`
- `ChannelGatewayContext`
## Example: Windows command shim fixture
```ts
import { createWindowsCmdShimFixture } from "openclaw/plugin-sdk/testing";
import { describe, expect, it } from "vitest";
describe("example CLI integration", () => {
it("creates a command shim", async () => {
await createWindowsCmdShimFixture({
shimPath: "/tmp/example.cmd",
scriptPath: "/tmp/example.js",
shimLine: 'node "%~dp0\\example.js" %*',
});
expect(true).toBe(true);
});
});
```
## Example: shared target-resolution failures
```ts
import { installCommonResolveTargetErrorCases } from "openclaw/plugin-sdk/testing";
installCommonResolveTargetErrorCases({
implicitAllowFrom: ["user-1"],
resolveTarget({ to, mode, allowFrom }) {
if (!to?.trim()) {
return { ok: false, error: new Error("missing target") };
}
if (mode === "implicit" && allowFrom.length > 0 && to === "invalid-target") {
return { ok: false, error: new Error("invalid target") };
}
return { ok: true, to };
},
});
```
## Runtime doubles
There is no catch-all `createTestRuntime()` export on the public SDK today.
Instead:
- use the public testing helpers where they fit
- use `plugin-sdk/runtime` for small runtime adapters
- build tiny plugin-local runtime doubles for the rest
Example:
```ts
import { createLoggerBackedRuntime } from "openclaw/plugin-sdk/runtime";
const logs: string[] = [];
const runtime = createLoggerBackedRuntime({
logger: {
info(message) {
logs.push(`info:${message}`);
},
error(message) {
logs.push(`error:${message}`);
},
},
});
```
## Test guidance
- Prefer focused unit tests over giant end-to-end harnesses.
- Import pure types from focused SDK subpaths in tests.
- Keep plugin-local test doubles small and explicit.
- Avoid depending on non-exported OpenClaw test internals.
## Related
- [Building Plugins](/plugins/building-plugins)
- [Plugin SDK Overview](/plugins/sdk-overview)
- [Plugin Runtime](/plugins/sdk-runtime)

View File

@@ -56,6 +56,9 @@ OpenClaw recognizes two plugin formats:
Both show up under `openclaw plugins list`. See [Plugin Bundles](/plugins/bundles) for bundle details.
If you are writing a native plugin, start with [Building Plugins](/plugins/building-plugins)
and the [Plugin SDK Overview](/plugins/sdk-overview).
## Official plugins
### Installable (npm)

View File

@@ -14,10 +14,12 @@ export type ChannelId = ChatChannelId | (string & {});
export type ChannelOutboundTargetMode = "explicit" | "implicit" | "heartbeat";
/** Agent tool registered by a channel plugin. */
export type ChannelAgentTool = AgentTool<TSchema, unknown> & {
ownerOnly?: boolean;
};
/** Lazy agent-tool factory used when tool availability depends on config. */
export type ChannelAgentToolFactory = (params: { cfg?: OpenClawConfig }) => ChannelAgentTool[];
/**
@@ -57,6 +59,7 @@ export type ChannelMessageToolDiscovery = {
schema?: ChannelMessageToolSchemaContribution | ChannelMessageToolSchemaContribution[] | null;
};
/** Shared setup input bag used by CLI, onboarding, and setup adapters. */
export type ChannelSetupInput = {
name?: string;
token?: string;
@@ -115,6 +118,7 @@ export type ChannelHeartbeatDeps = {
hasActiveWebListener?: () => boolean;
};
/** User-facing metadata used in docs, pickers, and setup surfaces. */
export type ChannelMeta = {
id: ChannelId;
label: string;
@@ -136,6 +140,7 @@ export type ChannelMeta = {
preferOver?: string[];
};
/** Snapshot row returned by channel status and lifecycle surfaces. */
export type ChannelAccountSnapshot = {
accountId: string;
name?: string;
@@ -220,6 +225,7 @@ export type ChannelGroupContext = {
senderE164?: string | null;
};
/** Static capability flags advertised by a channel plugin. */
export type ChannelCapabilities = {
chatTypes: Array<ChatType | "thread">;
polls?: boolean;
@@ -384,6 +390,7 @@ export type ChannelThreadingToolContext = {
skipCrossContextDecoration?: boolean;
};
/** Channel-owned messaging helpers for target parsing, routing, and payload shaping. */
export type ChannelMessagingAdapter = {
normalizeTarget?: (raw: string) => string | undefined;
resolveSessionTarget?: (params: {
@@ -470,6 +477,7 @@ export type ChannelDirectoryEntry = {
export type ChannelMessageActionName = ChannelMessageActionNameFromList;
/** Execution context passed to channel-owned actions on the shared `message` tool. */
export type ChannelMessageActionContext = {
channel: ChannelId;
action: ChannelMessageActionName;
@@ -503,6 +511,7 @@ export type ChannelToolSend = {
threadId?: string | null;
};
/** Channel-owned action surface for the shared `message` tool. */
export type ChannelMessageActionAdapter = {
/**
* Unified discovery surface for the shared `message` tool.
@@ -533,6 +542,7 @@ export type ChannelPollResult = {
pollId?: string;
};
/** Shared poll input after core has normalized the common poll model. */
export type ChannelPollContext = {
cfg: OpenClawConfig;
to: string;

View File

@@ -44,11 +44,13 @@ export type ChannelConfigUiHint = {
itemTemplate?: unknown;
};
/** JSON-schema-like config description published by a channel plugin. */
export type ChannelConfigSchema = {
schema: Record<string, unknown>;
uiHints?: Record<string, ChannelConfigUiHint>;
};
/** Full capability contract for a native channel plugin. */
// oxlint-disable-next-line typescript/no-explicit-any
export type ChannelPlugin<ResolvedAccount = any, Probe = unknown, Audit = unknown> = {
id: ChannelId;

View File

@@ -7,6 +7,7 @@ import { Type } from "@sinclair/typebox";
import type { TSchema } from "@sinclair/typebox";
import { stringEnum } from "../agents/schema/typebox.js";
/** Schema helper for channels that expose button rows on the shared `message` tool. */
export function createMessageToolButtonsSchema(): TSchema {
return Type.Array(
Type.Array(
@@ -22,6 +23,7 @@ export function createMessageToolButtonsSchema(): TSchema {
);
}
/** Schema helper for channels that accept provider-native card payloads. */
export function createMessageToolCardSchema(): TSchema {
return Type.Object(
{},

View File

@@ -1,3 +1,4 @@
// Pure channel contract types used by plugin implementations and tests.
export type {
BaseProbeResult,
BaseTokenResolution,

View File

@@ -1,3 +1,4 @@
// Shared feedback helpers for typing indicators, ack reactions, and status reactions.
export {
removeAckReactionAfterReply,
shouldAckReaction,

View File

@@ -1,3 +1,4 @@
// Shared inbound parsing helpers for channel plugins.
export {
createInboundDebouncer,
resolveInboundDebounceMs,

View File

@@ -10,12 +10,14 @@ import { createScopedPairingAccess } from "./pairing-access.js";
type ScopedPairingAccess = ReturnType<typeof createScopedPairingAccess>;
/** Pairing helpers scoped to one channel account. */
export type ChannelPairingController = ScopedPairingAccess & {
issueChallenge: (
params: Omit<Parameters<typeof issuePairingChallenge>[0], "channel" | "upsertPairingRequest">,
) => ReturnType<typeof issuePairingChallenge>;
};
/** Pre-bind the channel id and storage sink for pairing challenges. */
export function createChannelPairingChallengeIssuer(params: {
channel: ChannelId;
upsertPairingRequest: Parameters<typeof issuePairingChallenge>[0]["upsertPairingRequest"];
@@ -33,6 +35,7 @@ export function createChannelPairingChallengeIssuer(params: {
});
}
/** Build the full scoped pairing controller used by channel runtime code. */
export function createChannelPairingController(params: {
core: PluginRuntime;
channel: ChannelId;

View File

@@ -17,6 +17,7 @@ export {
splitSetupEntries,
} from "./setup.js";
/** Metadata used to advertise an optional channel plugin during setup flows. */
type OptionalChannelSetupParams = {
channel: string;
label: string;
@@ -24,6 +25,7 @@ type OptionalChannelSetupParams = {
docsPath?: string;
};
/** Paired setup adapter + setup wizard for channels that may not be installed yet. */
export type OptionalChannelSetupSurface = {
setupAdapter: ChannelSetupAdapter;
setupWizard: ChannelSetupWizard;
@@ -34,6 +36,7 @@ export {
createOptionalChannelSetupWizard,
} from "./optional-channel-setup.js";
/** Build both optional setup surfaces from one metadata object. */
export function createOptionalChannelSetupSurface(
params: OptionalChannelSetupParams,
): OptionalChannelSetupSurface {

View File

@@ -137,6 +137,7 @@ export type ChannelOutboundSessionRouteParams = Parameters<
NonNullable<ChannelMessagingAdapter["resolveOutboundSessionRoute"]>
>[0];
/** Remove one of the known provider prefixes from a free-form target string. */
export function stripChannelTargetPrefix(raw: string, ...providers: string[]): string {
const trimmed = raw.trim();
for (const provider of providers) {
@@ -148,10 +149,15 @@ export function stripChannelTargetPrefix(raw: string, ...providers: string[]): s
return trimmed;
}
/** Remove generic target-kind prefixes such as `user:` or `group:`. */
export function stripTargetKindPrefix(raw: string): string {
return raw.replace(/^(user|channel|group|conversation|room|dm):/i, "").trim();
}
/**
* Build the canonical outbound session route payload returned by channel
* message adapters.
*/
export function buildChannelOutboundSessionRoute(params: {
cfg: OpenClawConfig;
agentId: string;
@@ -181,6 +187,7 @@ export function buildChannelOutboundSessionRoute(params: {
};
}
/** Options for a channel plugin entry that should register a channel capability. */
type DefineChannelPluginEntryOptions<TPlugin extends ChannelPlugin = ChannelPlugin> = {
id: string;
name: string;
@@ -227,7 +234,13 @@ type CreatedChannelPluginBase<TResolvedAccount> = Pick<
>
>;
// Shared channel-plugin entry boilerplate for bundled and third-party channels.
/**
* Canonical entry helper for channel plugins.
*
* This wraps `definePluginEntry(...)`, registers the channel capability, and
* optionally exposes extra full-runtime registration such as tools or gateway
* handlers that only make sense outside setup-only registration modes.
*/
export function defineChannelPluginEntry<TPlugin extends ChannelPlugin>({
id,
name,
@@ -253,7 +266,12 @@ export function defineChannelPluginEntry<TPlugin extends ChannelPlugin>({
});
}
// Shared setup-entry shape so bundled channels do not duplicate `{ plugin }`.
/**
* Minimal setup-entry helper for channels that ship a separate `setup-entry.ts`.
*
* The setup entry only needs to export `{ plugin }`, but using this helper
* keeps the shape explicit in examples and generated typings.
*/
export function defineSetupPluginEntry<TPlugin>(plugin: TPlugin) {
return { plugin };
}

View File

@@ -52,6 +52,7 @@ export type { OpenClawConfig } from "../config/config.js";
export { emptyPluginConfigSchema } from "../plugins/config-schema.js";
/** Options for a plugin entry that registers providers, tools, commands, or services. */
type DefinePluginEntryOptions = {
id: string;
name: string;
@@ -61,6 +62,7 @@ type DefinePluginEntryOptions = {
register: (api: OpenClawPluginApi) => void;
};
/** Normalized object shape that OpenClaw loads from a plugin entry module. */
type DefinedPluginEntry = {
id: string;
name: string;
@@ -69,13 +71,20 @@ type DefinedPluginEntry = {
register: NonNullable<OpenClawPluginDefinition["register"]>;
} & Pick<OpenClawPluginDefinition, "kind">;
/** Resolve either a concrete config schema or a lazy schema factory. */
function resolvePluginConfigSchema(
configSchema: DefinePluginEntryOptions["configSchema"] = emptyPluginConfigSchema,
): OpenClawPluginConfigSchema {
return typeof configSchema === "function" ? configSchema() : configSchema;
}
// Small entry surface for provider and command plugins that do not need channel helpers.
/**
* Canonical entry helper for non-channel plugins.
*
* Use this for provider, tool, command, service, memory, and context-engine
* plugins. Channel plugins should use `defineChannelPluginEntry(...)` from
* `openclaw/plugin-sdk/core` so they inherit the channel capability wiring.
*/
export function definePluginEntry({
id,
name,

View File

@@ -1,4 +1,5 @@
// Public auth/onboarding helpers for provider plugins.
// Curated auth + onboarding helpers for provider plugins.
// Keep this surface focused on reusable provider-owned login flows.
export type { OpenClawConfig } from "../config/config.js";
export type { SecretInput } from "../config/types.secrets.js";

View File

@@ -1,4 +1,4 @@
// Public config patch helpers for provider onboarding flows.
// Curated config-patch helpers for provider onboarding flows.
export type { OpenClawConfig } from "../config/config.js";
export type {

View File

@@ -1,3 +1,4 @@
// Curated setup helpers for provider plugins that integrate local/self-hosted models.
export type {
OpenClawPluginApi,
ProviderAuthContext,

View File

@@ -19,6 +19,7 @@ export * from "../logging.js";
export { waitForAbortSignal } from "../infra/abort-signal.js";
export { registerUnhandledRejectionHandler } from "../infra/unhandled-rejections.js";
/** Minimal logger contract accepted by runtime-adapter helpers. */
type LoggerLike = {
info: (message: string) => void;
error: (message: string) => void;

View File

@@ -14,10 +14,12 @@ export {
normalizeSecretInputString,
};
/** Optional version of the shared secret-input schema. */
export function buildOptionalSecretInputSchema() {
return buildSecretInputSchema().optional();
}
/** Array version of the shared secret-input schema. */
export function buildSecretInputArraySchema() {
return z.array(buildSecretInputSchema());
}

View File

@@ -1,3 +1,4 @@
// Focused self-hosted provider setup helpers for OpenAI-compatible backends.
export type {
OpenClawPluginApi,
ProviderAuthContext,

View File

@@ -1,4 +1,4 @@
// Shared setup wizard/types/helpers for extension setup surfaces and adapters.
// Shared setup wizard/types/helpers for plugin and channel setup surfaces.
export type { OpenClawConfig } from "../config/config.js";
export type { DmPolicy, GroupPolicy } from "../config/types.js";

View File

@@ -12,6 +12,7 @@ export type { PluginRuntime } from "../plugins/runtime/types.js";
export type { RuntimeEnv } from "../runtime.js";
export type { MockFn } from "../test-utils/vitest-mock-fn.js";
/** Create a tiny Windows `.cmd` shim fixture for plugin tests that spawn CLIs. */
export async function createWindowsCmdShimFixture(params: {
shimPath: string;
scriptPath: string;
@@ -37,6 +38,7 @@ type ResolveTargetFn = (params: {
allowFrom: string[];
}) => ResolveTargetResult;
/** Install a shared test matrix for target-resolution error handling. */
export function installCommonResolveTargetErrorCases(params: {
resolveTarget: ResolveTargetFn;
implicitAllowFrom: string[];

View File

@@ -1,3 +1,4 @@
// Curated webhook helpers for plugin-owned HTTP ingress and webhook targets.
export {
createBoundedCounter,
createFixedWindowRateLimiter,

View File

@@ -1,5 +1,6 @@
import type { LogLevel } from "../../logging/levels.js";
/** Structured logger surface injected into runtime-backed plugin helpers. */
export type RuntimeLogger = {
debug?: (message: string, meta?: Record<string, unknown>) => void;
info: (message: string, meta?: Record<string, unknown>) => void;
@@ -7,6 +8,7 @@ export type RuntimeLogger = {
error: (message: string, meta?: Record<string, unknown>) => void;
};
/** Core runtime helpers exposed to trusted native plugins. */
export type PluginRuntimeCore = {
version: string;
config: {

View File

@@ -50,6 +50,7 @@ export type SubagentDeleteSessionParams = {
deleteTranscript?: boolean;
};
/** Trusted in-process runtime surface injected into native plugins. */
export type PluginRuntime = PluginRuntimeCore & {
subagent: {
run: (params: SubagentRunParams) => Promise<SubagentRunResult>;

View File

@@ -55,6 +55,7 @@ export type ProviderAuthOptionBag = {
[key: string]: unknown;
};
/** Logger passed into plugin registration, services, and CLI surfaces. */
export type PluginLogger = {
debug?: (message: string) => void;
info: (message: string) => void;
@@ -77,6 +78,13 @@ export type PluginConfigValidation =
| { ok: true; value?: unknown }
| { ok: false; errors: string[] };
/**
* Config schema contract accepted by plugin manifests and runtime registration.
*
* Plugins can provide a Zod-like parser, a lightweight `validate(...)`
* function, or both. `uiHints` and `jsonSchema` are optional extras for docs,
* forms, and config UIs.
*/
export type OpenClawPluginConfigSchema = {
safeParse?: (value: unknown) => {
success: boolean;
@@ -91,6 +99,7 @@ export type OpenClawPluginConfigSchema = {
jsonSchema?: Record<string, unknown>;
};
/** Trusted execution context passed to plugin-owned agent tool factories. */
export type OpenClawPluginToolContext = {
config?: OpenClawConfig;
workspaceDir?: string;
@@ -127,6 +136,7 @@ export type OpenClawPluginHookOptions = {
export type ProviderAuthKind = "oauth" | "api_key" | "token" | "device_code" | "custom";
/** Standard result payload returned by provider auth methods. */
export type ProviderAuthResult = {
profiles: Array<{ profileId: string; credential: AuthProfileCredential }>;
/**
@@ -141,6 +151,7 @@ export type ProviderAuthResult = {
notes?: string[];
};
/** Interactive auth context passed to provider login/setup methods. */
export type ProviderAuthContext = {
config: OpenClawConfig;
agentDir?: string;
@@ -612,12 +623,14 @@ export type ProviderPluginWizardSetup = {
};
};
/** Optional model-picker metadata shown in interactive provider selection flows. */
export type ProviderPluginWizardModelPicker = {
label?: string;
hint?: string;
methodId?: string;
};
/** UI metadata that lets provider plugins appear in onboarding and configure flows. */
export type ProviderPluginWizard = {
setup?: ProviderPluginWizardSetup;
modelPicker?: ProviderPluginWizardModelPicker;
@@ -631,6 +644,7 @@ export type ProviderModelSelectedContext = {
workspaceDir?: string;
};
/** Text-inference provider capability registered by a plugin. */
export type ProviderPlugin = {
id: string;
pluginId?: string;
@@ -914,6 +928,7 @@ export type PluginWebSearchProviderEntry = WebSearchProviderPlugin & {
pluginId: string;
};
/** Speech capability registered by a plugin. */
export type SpeechProviderPlugin = {
id: SpeechProviderId;
label: string;
@@ -1255,6 +1270,7 @@ export type OpenClawPluginCliContext = {
export type OpenClawPluginCliRegistrar = (ctx: OpenClawPluginCliContext) => void | Promise<void>;
/** Context passed to long-lived plugin services. */
export type OpenClawPluginServiceContext = {
config: OpenClawConfig;
workspaceDir?: string;
@@ -1262,6 +1278,7 @@ export type OpenClawPluginServiceContext = {
logger: PluginLogger;
};
/** Background service registered by a plugin during `register(api)`. */
export type OpenClawPluginService = {
id: string;
start: (ctx: OpenClawPluginServiceContext) => void | Promise<void>;
@@ -1272,6 +1289,7 @@ export type OpenClawPluginChannelRegistration = {
plugin: ChannelPlugin;
};
/** Module-level plugin definition loaded from a native plugin entry file. */
export type OpenClawPluginDefinition = {
id?: string;
name?: string;
@@ -1289,6 +1307,7 @@ export type OpenClawPluginModule =
export type PluginRegistrationMode = "full" | "setup-only" | "setup-runtime";
/** Main registration API injected into native plugin entry files. */
export type OpenClawPluginApi = {
id: string;
name: string;