mirror of https://github.com/openclaw/openclaw.git
docs: explain plugin architecture
This commit is contained in:
parent
d83491e751
commit
319766639a
|
|
@ -352,7 +352,7 @@ See [/providers/minimax](/providers/minimax) for setup details, model options, a
|
||||||
|
|
||||||
### Ollama
|
### Ollama
|
||||||
|
|
||||||
Ollama is a local LLM runtime that provides an OpenAI-compatible API:
|
Ollama ships as a bundled provider plugin and uses Ollama's native API:
|
||||||
|
|
||||||
- Provider: `ollama`
|
- Provider: `ollama`
|
||||||
- Auth: None required (local server)
|
- Auth: None required (local server)
|
||||||
|
|
@ -372,11 +372,15 @@ ollama pull llama3.3
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
Ollama is detected locally at `http://127.0.0.1:11434` when you opt in with `OLLAMA_API_KEY`, and `openclaw onboard` can configure it directly as a first-class provider. See [/providers/ollama](/providers/ollama) for onboarding, cloud/local mode, and custom configuration.
|
Ollama is detected locally at `http://127.0.0.1:11434` when you opt in with
|
||||||
|
`OLLAMA_API_KEY`, and the bundled provider plugin adds Ollama directly to
|
||||||
|
`openclaw onboard` and the model picker. See [/providers/ollama](/providers/ollama)
|
||||||
|
for onboarding, cloud/local mode, and custom configuration.
|
||||||
|
|
||||||
### vLLM
|
### vLLM
|
||||||
|
|
||||||
vLLM is a local (or self-hosted) OpenAI-compatible server:
|
vLLM ships as a bundled provider plugin for local/self-hosted OpenAI-compatible
|
||||||
|
servers:
|
||||||
|
|
||||||
- Provider: `vllm`
|
- Provider: `vllm`
|
||||||
- Auth: Optional (depends on your server)
|
- Auth: Optional (depends on your server)
|
||||||
|
|
@ -400,6 +404,34 @@ Then set a model (replace with one of the IDs returned by `/v1/models`):
|
||||||
|
|
||||||
See [/providers/vllm](/providers/vllm) for details.
|
See [/providers/vllm](/providers/vllm) for details.
|
||||||
|
|
||||||
|
### SGLang
|
||||||
|
|
||||||
|
SGLang ships as a bundled provider plugin for fast self-hosted
|
||||||
|
OpenAI-compatible servers:
|
||||||
|
|
||||||
|
- Provider: `sglang`
|
||||||
|
- Auth: Optional (depends on your server)
|
||||||
|
- Default base URL: `http://127.0.0.1:30000/v1`
|
||||||
|
|
||||||
|
To opt in to auto-discovery locally (any value works if your server does not
|
||||||
|
enforce auth):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
export SGLANG_API_KEY="sglang-local"
|
||||||
|
```
|
||||||
|
|
||||||
|
Then set a model (replace with one of the IDs returned by `/v1/models`):
|
||||||
|
|
||||||
|
```json5
|
||||||
|
{
|
||||||
|
agents: {
|
||||||
|
defaults: { model: { primary: "sglang/your-model-id" } },
|
||||||
|
},
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
See [/providers/sglang](/providers/sglang) for details.
|
||||||
|
|
||||||
### Local proxies (LM Studio, vLLM, LiteLLM, etc.)
|
### Local proxies (LM Studio, vLLM, LiteLLM, etc.)
|
||||||
|
|
||||||
Example (OpenAI‑compatible):
|
Example (OpenAI‑compatible):
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,104 @@
|
||||||
|
---
|
||||||
|
summary: "Run OpenClaw with SGLang (OpenAI-compatible self-hosted server)"
|
||||||
|
read_when:
|
||||||
|
- You want to run OpenClaw against a local SGLang server
|
||||||
|
- You want OpenAI-compatible /v1 endpoints with your own models
|
||||||
|
title: "SGLang"
|
||||||
|
---
|
||||||
|
|
||||||
|
# SGLang
|
||||||
|
|
||||||
|
SGLang can serve open-source models via an **OpenAI-compatible** HTTP API.
|
||||||
|
OpenClaw can connect to SGLang using the `openai-completions` API.
|
||||||
|
|
||||||
|
OpenClaw can also **auto-discover** available models from SGLang when you opt
|
||||||
|
in with `SGLANG_API_KEY` (any value works if your server does not enforce auth)
|
||||||
|
and you do not define an explicit `models.providers.sglang` entry.
|
||||||
|
|
||||||
|
## Quick start
|
||||||
|
|
||||||
|
1. Start SGLang with an OpenAI-compatible server.
|
||||||
|
|
||||||
|
Your base URL should expose `/v1` endpoints (for example `/v1/models`,
|
||||||
|
`/v1/chat/completions`). SGLang commonly runs on:
|
||||||
|
|
||||||
|
- `http://127.0.0.1:30000/v1`
|
||||||
|
|
||||||
|
2. Opt in (any value works if no auth is configured):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
export SGLANG_API_KEY="sglang-local"
|
||||||
|
```
|
||||||
|
|
||||||
|
3. Run onboarding and choose `SGLang`, or set a model directly:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
openclaw onboard
|
||||||
|
```
|
||||||
|
|
||||||
|
```json5
|
||||||
|
{
|
||||||
|
agents: {
|
||||||
|
defaults: {
|
||||||
|
model: { primary: "sglang/your-model-id" },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Model discovery (implicit provider)
|
||||||
|
|
||||||
|
When `SGLANG_API_KEY` is set (or an auth profile exists) and you **do not**
|
||||||
|
define `models.providers.sglang`, OpenClaw will query:
|
||||||
|
|
||||||
|
- `GET http://127.0.0.1:30000/v1/models`
|
||||||
|
|
||||||
|
and convert the returned IDs into model entries.
|
||||||
|
|
||||||
|
If you set `models.providers.sglang` explicitly, auto-discovery is skipped and
|
||||||
|
you must define models manually.
|
||||||
|
|
||||||
|
## Explicit configuration (manual models)
|
||||||
|
|
||||||
|
Use explicit config when:
|
||||||
|
|
||||||
|
- SGLang runs on a different host/port.
|
||||||
|
- You want to pin `contextWindow`/`maxTokens` values.
|
||||||
|
- Your server requires a real API key (or you want to control headers).
|
||||||
|
|
||||||
|
```json5
|
||||||
|
{
|
||||||
|
models: {
|
||||||
|
providers: {
|
||||||
|
sglang: {
|
||||||
|
baseUrl: "http://127.0.0.1:30000/v1",
|
||||||
|
apiKey: "${SGLANG_API_KEY}",
|
||||||
|
api: "openai-completions",
|
||||||
|
models: [
|
||||||
|
{
|
||||||
|
id: "your-model-id",
|
||||||
|
name: "Local SGLang Model",
|
||||||
|
reasoning: false,
|
||||||
|
input: ["text"],
|
||||||
|
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
||||||
|
contextWindow: 128000,
|
||||||
|
maxTokens: 8192,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
- Check the server is reachable:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl http://127.0.0.1:30000/v1/models
|
||||||
|
```
|
||||||
|
|
||||||
|
- If requests fail with auth errors, set a real `SGLANG_API_KEY` that matches
|
||||||
|
your server configuration, or configure the provider explicitly under
|
||||||
|
`models.providers.sglang`.
|
||||||
|
|
@ -43,6 +43,48 @@ prerelease tag such as `@beta`/`@rc` or an exact prerelease version.
|
||||||
See [Voice Call](/plugins/voice-call) for a concrete example plugin.
|
See [Voice Call](/plugins/voice-call) for a concrete example plugin.
|
||||||
Looking for third-party listings? See [Community plugins](/plugins/community).
|
Looking for third-party listings? See [Community plugins](/plugins/community).
|
||||||
|
|
||||||
|
## Architecture
|
||||||
|
|
||||||
|
OpenClaw's plugin system has four layers:
|
||||||
|
|
||||||
|
1. **Manifest + discovery**
|
||||||
|
OpenClaw finds candidate plugins from configured paths, workspace roots,
|
||||||
|
global extension roots, and bundled extensions. Discovery reads
|
||||||
|
`openclaw.plugin.json` plus package metadata first.
|
||||||
|
2. **Enablement + validation**
|
||||||
|
Core decides whether a discovered plugin is enabled, disabled, blocked, or
|
||||||
|
selected for an exclusive slot such as memory.
|
||||||
|
3. **Runtime loading**
|
||||||
|
Enabled plugins are loaded in-process via jiti and register capabilities into
|
||||||
|
a central registry.
|
||||||
|
4. **Surface consumption**
|
||||||
|
The rest of OpenClaw reads the registry to expose tools, channels, provider
|
||||||
|
setup, hooks, HTTP routes, CLI commands, and services.
|
||||||
|
|
||||||
|
The important design boundary:
|
||||||
|
|
||||||
|
- discovery + config validation should work from **manifest/schema metadata**
|
||||||
|
without executing plugin code
|
||||||
|
- runtime behavior comes from the plugin module's `register(api)` path
|
||||||
|
|
||||||
|
That split lets OpenClaw validate config, explain missing/disabled plugins, and
|
||||||
|
build UI/schema hints before the full runtime is active.
|
||||||
|
|
||||||
|
## Execution model
|
||||||
|
|
||||||
|
Plugins run **in-process** with the Gateway. They are not sandboxed. A loaded
|
||||||
|
plugin has the same process-level trust boundary as core code.
|
||||||
|
|
||||||
|
Implications:
|
||||||
|
|
||||||
|
- a plugin can register tools, network handlers, hooks, and services
|
||||||
|
- a plugin bug can crash or destabilize the gateway
|
||||||
|
- a malicious plugin is equivalent to arbitrary code execution inside the
|
||||||
|
OpenClaw process
|
||||||
|
|
||||||
|
Use allowlists and explicit install/load paths for non-bundled plugins. Treat
|
||||||
|
workspace plugins as development-time code, not production defaults.
|
||||||
|
|
||||||
## Available plugins (official)
|
## Available plugins (official)
|
||||||
|
|
||||||
- Microsoft Teams is plugin-only as of 2026.1.15; install `@openclaw/msteams` if you use Teams.
|
- Microsoft Teams is plugin-only as of 2026.1.15; install `@openclaw/msteams` if you use Teams.
|
||||||
|
|
@ -78,6 +120,48 @@ Plugins can register:
|
||||||
Plugins run **in‑process** with the Gateway, so treat them as trusted code.
|
Plugins run **in‑process** with the Gateway, so treat them as trusted code.
|
||||||
Tool authoring guide: [Plugin agent tools](/plugins/agent-tools).
|
Tool authoring guide: [Plugin agent tools](/plugins/agent-tools).
|
||||||
|
|
||||||
|
## Load pipeline
|
||||||
|
|
||||||
|
At startup, OpenClaw does roughly this:
|
||||||
|
|
||||||
|
1. discover candidate plugin roots
|
||||||
|
2. read `openclaw.plugin.json` and package metadata
|
||||||
|
3. reject unsafe candidates
|
||||||
|
4. normalize plugin config (`plugins.enabled`, `allow`, `deny`, `entries`,
|
||||||
|
`slots`, `load.paths`)
|
||||||
|
5. decide enablement for each candidate
|
||||||
|
6. load enabled modules via jiti
|
||||||
|
7. call `register(api)` and collect registrations into the plugin registry
|
||||||
|
8. expose the registry to commands/runtime surfaces
|
||||||
|
|
||||||
|
The safety gates happen **before** runtime execution. Candidates are blocked
|
||||||
|
when the entry escapes the plugin root, the path is world-writable, or path
|
||||||
|
ownership looks suspicious for non-bundled plugins.
|
||||||
|
|
||||||
|
### Manifest-first behavior
|
||||||
|
|
||||||
|
The manifest is the control-plane source of truth. OpenClaw uses it to:
|
||||||
|
|
||||||
|
- identify the plugin
|
||||||
|
- discover declared channels/skills/config schema
|
||||||
|
- validate `plugins.entries.<id>.config`
|
||||||
|
- augment Control UI labels/placeholders
|
||||||
|
- show install/catalog metadata
|
||||||
|
|
||||||
|
The runtime module is the data-plane part. It registers actual behavior such as
|
||||||
|
hooks, tools, commands, or provider flows.
|
||||||
|
|
||||||
|
### What the loader caches
|
||||||
|
|
||||||
|
OpenClaw keeps short in-process caches for:
|
||||||
|
|
||||||
|
- discovery results
|
||||||
|
- manifest registry data
|
||||||
|
- loaded plugin registries
|
||||||
|
|
||||||
|
These caches reduce bursty startup and repeated command overhead. They are safe
|
||||||
|
to think of as short-lived performance caches, not persistence.
|
||||||
|
|
||||||
## Runtime helpers
|
## Runtime helpers
|
||||||
|
|
||||||
Plugins can access selected core helpers via `api.runtime`. For telephony TTS:
|
Plugins can access selected core helpers via `api.runtime`. For telephony TTS:
|
||||||
|
|
@ -259,6 +343,10 @@ Default-on bundled plugin exceptions:
|
||||||
|
|
||||||
Installed plugins are enabled by default, but can be disabled the same way.
|
Installed plugins are enabled by default, but can be disabled the same way.
|
||||||
|
|
||||||
|
Workspace plugins are **disabled by default** unless you explicitly enable them
|
||||||
|
or allowlist them. This is intentional: a checked-out repo should not silently
|
||||||
|
become production gateway code.
|
||||||
|
|
||||||
Hardening notes:
|
Hardening notes:
|
||||||
|
|
||||||
- If `plugins.allow` is empty and non-bundled plugins are discoverable, OpenClaw logs a startup warning with plugin ids and sources.
|
- If `plugins.allow` is empty and non-bundled plugins are discoverable, OpenClaw logs a startup warning with plugin ids and sources.
|
||||||
|
|
@ -275,6 +363,25 @@ manifest.
|
||||||
If multiple plugins resolve to the same id, the first match in the order above
|
If multiple plugins resolve to the same id, the first match in the order above
|
||||||
wins and lower-precedence copies are ignored.
|
wins and lower-precedence copies are ignored.
|
||||||
|
|
||||||
|
### Enablement rules
|
||||||
|
|
||||||
|
Enablement is resolved after discovery:
|
||||||
|
|
||||||
|
- `plugins.enabled: false` disables all plugins
|
||||||
|
- `plugins.deny` always wins
|
||||||
|
- `plugins.entries.<id>.enabled: false` disables that plugin
|
||||||
|
- workspace-origin plugins are disabled by default
|
||||||
|
- allowlists restrict the active set when `plugins.allow` is non-empty
|
||||||
|
- bundled plugins are disabled by default unless:
|
||||||
|
- the bundled id is in the built-in default-on set, or
|
||||||
|
- you explicitly enable it, or
|
||||||
|
- channel config implicitly enables the bundled channel plugin
|
||||||
|
- exclusive slots can force-enable the selected plugin for that slot
|
||||||
|
|
||||||
|
In current core, bundled default-on ids include local/provider helpers such as
|
||||||
|
`ollama`, `sglang`, `vllm`, plus `device-pair`, `phone-control`, and
|
||||||
|
`talk-voice`.
|
||||||
|
|
||||||
### Package packs
|
### Package packs
|
||||||
|
|
||||||
A plugin directory may include a `package.json` with `openclaw.extensions`:
|
A plugin directory may include a `package.json` with `openclaw.extensions`:
|
||||||
|
|
@ -354,6 +461,34 @@ Default plugin ids:
|
||||||
If a plugin exports `id`, OpenClaw uses it but warns when it doesn’t match the
|
If a plugin exports `id`, OpenClaw uses it but warns when it doesn’t match the
|
||||||
configured id.
|
configured id.
|
||||||
|
|
||||||
|
## Registry model
|
||||||
|
|
||||||
|
Loaded plugins do not directly mutate random core globals. They register into a
|
||||||
|
central plugin registry.
|
||||||
|
|
||||||
|
The registry tracks:
|
||||||
|
|
||||||
|
- plugin records (identity, source, origin, status, diagnostics)
|
||||||
|
- tools
|
||||||
|
- legacy hooks and typed hooks
|
||||||
|
- channels
|
||||||
|
- providers
|
||||||
|
- gateway RPC handlers
|
||||||
|
- HTTP routes
|
||||||
|
- CLI registrars
|
||||||
|
- background services
|
||||||
|
- plugin-owned commands
|
||||||
|
|
||||||
|
Core features then read from that registry instead of talking to plugin modules
|
||||||
|
directly. This keeps loading one-way:
|
||||||
|
|
||||||
|
- plugin module -> registry registration
|
||||||
|
- core runtime -> registry consumption
|
||||||
|
|
||||||
|
That separation matters for maintainability. It means most core surfaces only
|
||||||
|
need one integration point: "read the registry", not "special-case every plugin
|
||||||
|
module".
|
||||||
|
|
||||||
## Config
|
## Config
|
||||||
|
|
||||||
```json5
|
```json5
|
||||||
|
|
@ -390,6 +525,17 @@ Validation rules (strict):
|
||||||
`openclaw.plugin.json` (`configSchema`).
|
`openclaw.plugin.json` (`configSchema`).
|
||||||
- If a plugin is disabled, its config is preserved and a **warning** is emitted.
|
- If a plugin is disabled, its config is preserved and a **warning** is emitted.
|
||||||
|
|
||||||
|
### Disabled vs missing vs invalid
|
||||||
|
|
||||||
|
These states are intentionally different:
|
||||||
|
|
||||||
|
- **disabled**: plugin exists, but enablement rules turned it off
|
||||||
|
- **missing**: config references a plugin id that discovery did not find
|
||||||
|
- **invalid**: plugin exists, but its config does not match the declared schema
|
||||||
|
|
||||||
|
OpenClaw preserves config for disabled plugins so toggling them back on is not
|
||||||
|
destructive.
|
||||||
|
|
||||||
## Plugin slots (exclusive categories)
|
## Plugin slots (exclusive categories)
|
||||||
|
|
||||||
Some plugin categories are **exclusive** (only one active at a time). Use
|
Some plugin categories are **exclusive** (only one active at a time). Use
|
||||||
|
|
@ -488,6 +634,19 @@ Plugins export either:
|
||||||
- A function: `(api) => { ... }`
|
- A function: `(api) => { ... }`
|
||||||
- An object: `{ id, name, configSchema, register(api) { ... } }`
|
- An object: `{ id, name, configSchema, register(api) { ... } }`
|
||||||
|
|
||||||
|
`register(api)` is where plugins attach behavior. Common registrations include:
|
||||||
|
|
||||||
|
- `registerTool`
|
||||||
|
- `registerHook`
|
||||||
|
- `on(...)` for typed lifecycle hooks
|
||||||
|
- `registerChannel`
|
||||||
|
- `registerProvider`
|
||||||
|
- `registerHttpRoute`
|
||||||
|
- `registerCommand`
|
||||||
|
- `registerCli`
|
||||||
|
- `registerContextEngine`
|
||||||
|
- `registerService`
|
||||||
|
|
||||||
Context engine plugins can also register a runtime-owned context manager:
|
Context engine plugins can also register a runtime-owned context manager:
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
|
|
@ -603,13 +762,150 @@ Migration guidance:
|
||||||
|
|
||||||
## Provider plugins (model auth)
|
## Provider plugins (model auth)
|
||||||
|
|
||||||
Plugins can register **model provider auth** flows so users can run OAuth or
|
Plugins can register **model providers** so users can run OAuth or API-key
|
||||||
API-key setup inside OpenClaw (no external scripts needed).
|
setup inside OpenClaw, surface provider setup in onboarding/model-pickers, and
|
||||||
|
contribute implicit provider discovery.
|
||||||
|
|
||||||
|
Provider plugins are the modular extension seam for model-provider setup. They
|
||||||
|
are not just "OAuth helpers" anymore.
|
||||||
|
|
||||||
|
### Provider plugin lifecycle
|
||||||
|
|
||||||
|
A provider plugin can participate in four distinct phases:
|
||||||
|
|
||||||
|
1. **Auth**
|
||||||
|
`auth[].run(ctx)` performs OAuth, API-key capture, device code, or custom
|
||||||
|
setup and returns auth profiles plus optional config patches.
|
||||||
|
2. **Wizard integration**
|
||||||
|
`wizard.onboarding` adds an entry to `openclaw onboard`.
|
||||||
|
`wizard.modelPicker` adds a setup entry to the model picker.
|
||||||
|
3. **Implicit discovery**
|
||||||
|
`discovery.run(ctx)` can contribute provider config automatically during
|
||||||
|
model resolution/listing.
|
||||||
|
4. **Post-selection follow-up**
|
||||||
|
`onModelSelected(ctx)` runs after a model is chosen. Use this for provider-
|
||||||
|
specific work such as downloading a local model.
|
||||||
|
|
||||||
|
This is the recommended split because these phases have different lifecycle
|
||||||
|
requirements:
|
||||||
|
|
||||||
|
- auth is interactive and writes credentials/config
|
||||||
|
- wizard metadata is static and UI-facing
|
||||||
|
- discovery should be safe, quick, and failure-tolerant
|
||||||
|
- post-select hooks are side effects tied to the chosen model
|
||||||
|
|
||||||
|
### Provider auth contract
|
||||||
|
|
||||||
|
`auth[].run(ctx)` returns:
|
||||||
|
|
||||||
|
- `profiles`: auth profiles to write
|
||||||
|
- `configPatch`: optional `openclaw.json` changes
|
||||||
|
- `defaultModel`: optional `provider/model` ref
|
||||||
|
- `notes`: optional user-facing notes
|
||||||
|
|
||||||
|
Core then:
|
||||||
|
|
||||||
|
1. writes the returned auth profiles
|
||||||
|
2. applies auth-profile config wiring
|
||||||
|
3. merges the config patch
|
||||||
|
4. optionally applies the default model
|
||||||
|
5. runs the provider's `onModelSelected` hook when appropriate
|
||||||
|
|
||||||
|
That means a provider plugin owns the provider-specific setup logic, while core
|
||||||
|
owns the generic persistence and config-merge path.
|
||||||
|
|
||||||
|
### Provider wizard metadata
|
||||||
|
|
||||||
|
`wizard.onboarding` controls how the provider appears in grouped onboarding:
|
||||||
|
|
||||||
|
- `choiceId`: auth-choice value
|
||||||
|
- `choiceLabel`: option label
|
||||||
|
- `choiceHint`: short hint
|
||||||
|
- `groupId`: group bucket id
|
||||||
|
- `groupLabel`: group label
|
||||||
|
- `groupHint`: group hint
|
||||||
|
- `methodId`: auth method to run
|
||||||
|
|
||||||
|
`wizard.modelPicker` controls how a provider appears as a "set this up now"
|
||||||
|
entry in model selection:
|
||||||
|
|
||||||
|
- `label`
|
||||||
|
- `hint`
|
||||||
|
- `methodId`
|
||||||
|
|
||||||
|
When a provider has multiple auth methods, the wizard can either point at one
|
||||||
|
explicit method or let OpenClaw synthesize per-method choices.
|
||||||
|
|
||||||
|
### Provider discovery contract
|
||||||
|
|
||||||
|
`discovery.run(ctx)` returns one of:
|
||||||
|
|
||||||
|
- `{ provider }`
|
||||||
|
- `{ providers }`
|
||||||
|
- `null`
|
||||||
|
|
||||||
|
Use `{ provider }` for the common case where the plugin owns one provider id.
|
||||||
|
Use `{ providers }` when a plugin discovers multiple provider entries.
|
||||||
|
|
||||||
|
The discovery context includes:
|
||||||
|
|
||||||
|
- the current config
|
||||||
|
- agent/workspace dirs
|
||||||
|
- process env
|
||||||
|
- a helper to resolve the provider API key and a discovery-safe API key value
|
||||||
|
|
||||||
|
Discovery should be:
|
||||||
|
|
||||||
|
- fast
|
||||||
|
- best-effort
|
||||||
|
- safe to skip on failure
|
||||||
|
- careful about side effects
|
||||||
|
|
||||||
|
It should not depend on prompts or long-running setup.
|
||||||
|
|
||||||
|
### Discovery ordering
|
||||||
|
|
||||||
|
Provider discovery runs in ordered phases:
|
||||||
|
|
||||||
|
- `simple`
|
||||||
|
- `profile`
|
||||||
|
- `paired`
|
||||||
|
- `late`
|
||||||
|
|
||||||
|
Use:
|
||||||
|
|
||||||
|
- `simple` for cheap environment-only discovery
|
||||||
|
- `profile` when discovery depends on auth profiles
|
||||||
|
- `paired` for providers that need to coordinate with another discovery step
|
||||||
|
- `late` for expensive or local-network probing
|
||||||
|
|
||||||
|
Most self-hosted providers should use `late`.
|
||||||
|
|
||||||
|
### Good provider-plugin boundaries
|
||||||
|
|
||||||
|
Good fit for provider plugins:
|
||||||
|
|
||||||
|
- local/self-hosted providers with custom setup flows
|
||||||
|
- provider-specific OAuth/device-code login
|
||||||
|
- implicit discovery of local model servers
|
||||||
|
- post-selection side effects such as model pulls
|
||||||
|
|
||||||
|
Less compelling fit:
|
||||||
|
|
||||||
|
- trivial API-key-only providers that differ only by env var, base URL, and one
|
||||||
|
default model
|
||||||
|
|
||||||
|
Those can still become plugins, but the main modularity payoff comes from
|
||||||
|
extracting behavior-rich providers first.
|
||||||
|
|
||||||
Register a provider via `api.registerProvider(...)`. Each provider exposes one
|
Register a provider via `api.registerProvider(...)`. Each provider exposes one
|
||||||
or more auth methods (OAuth, API key, device code, etc.). These methods power:
|
or more auth methods (OAuth, API key, device code, etc.). Those methods can
|
||||||
|
power:
|
||||||
|
|
||||||
- `openclaw models auth login --provider <id> [--method <id>]`
|
- `openclaw models auth login --provider <id> [--method <id>]`
|
||||||
|
- `openclaw onboard`
|
||||||
|
- model-picker “custom provider” setup entries
|
||||||
|
- implicit provider discovery during model resolution/listing
|
||||||
|
|
||||||
Example:
|
Example:
|
||||||
|
|
||||||
|
|
@ -642,6 +938,31 @@ api.registerProvider({
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
wizard: {
|
||||||
|
onboarding: {
|
||||||
|
choiceId: "acme",
|
||||||
|
choiceLabel: "AcmeAI",
|
||||||
|
groupId: "acme",
|
||||||
|
groupLabel: "AcmeAI",
|
||||||
|
methodId: "oauth",
|
||||||
|
},
|
||||||
|
modelPicker: {
|
||||||
|
label: "AcmeAI (custom)",
|
||||||
|
hint: "Connect a self-hosted AcmeAI endpoint",
|
||||||
|
methodId: "oauth",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
discovery: {
|
||||||
|
order: "late",
|
||||||
|
run: async () => ({
|
||||||
|
provider: {
|
||||||
|
baseUrl: "https://acme.example/v1",
|
||||||
|
api: "openai-completions",
|
||||||
|
apiKey: "${ACME_API_KEY}",
|
||||||
|
models: [],
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
},
|
||||||
});
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
@ -651,6 +972,14 @@ Notes:
|
||||||
`openUrl`, and `oauth.createVpsAwareHandlers` helpers.
|
`openUrl`, and `oauth.createVpsAwareHandlers` helpers.
|
||||||
- Return `configPatch` when you need to add default models or provider config.
|
- Return `configPatch` when you need to add default models or provider config.
|
||||||
- Return `defaultModel` so `--set-default` can update agent defaults.
|
- Return `defaultModel` so `--set-default` can update agent defaults.
|
||||||
|
- `wizard.onboarding` adds a provider choice to `openclaw onboard`.
|
||||||
|
- `wizard.modelPicker` adds a “setup this provider” entry to the model picker.
|
||||||
|
- `discovery.run` returns either `{ provider }` for the plugin’s own provider id
|
||||||
|
or `{ providers }` for multi-provider discovery.
|
||||||
|
- `discovery.order` controls when the provider runs relative to built-in
|
||||||
|
discovery phases: `simple`, `profile`, `paired`, or `late`.
|
||||||
|
- `onModelSelected` is the post-selection hook for provider-specific follow-up
|
||||||
|
work such as pulling a local model.
|
||||||
|
|
||||||
### Register a messaging channel
|
### Register a messaging channel
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue