mirror of
https://github.com/openclaw/openclaw.git
synced 2026-04-21 14:11:26 +00:00
fix: auto-load bundled plugin capabilities from config refs
This commit is contained in:
@@ -19,6 +19,7 @@ Docs: https://docs.openclaw.ai
|
||||
- Agents/compaction: preserve the post-compaction AGENTS refresh on stale-usage preflight compaction for both immediate replies and queued followups. (#49479) Thanks @jared596.
|
||||
- Agents/compaction: surface safeguard-specific cancel reasons and relabel benign manual `/compact` no-op cases as skipped instead of failed. (#51072) Thanks @afurm.
|
||||
- Plugins/CLI backends: move bundled Claude CLI, Codex CLI, and Gemini CLI inference defaults onto the plugin surface, add bundled Gemini CLI backend support, and replace `gateway run --claude-cli-logs` with generic `--cli-backend-logs` while keeping the old flag as a compatibility alias.
|
||||
- Plugins/startup: auto-load bundled provider and CLI-backend plugins from explicit config refs, so bundled Claude CLI, Codex CLI, and Gemini CLI message-provider setups no longer need manual `plugins.allow` entries.
|
||||
|
||||
### Fixes
|
||||
|
||||
|
||||
@@ -54,6 +54,11 @@ command path:
|
||||
|
||||
That’s it. No keys, no extra auth config needed beyond the CLI itself.
|
||||
|
||||
If you use a bundled CLI backend as the **primary message provider** on a
|
||||
gateway host, OpenClaw now auto-loads the owning bundled plugin when your config
|
||||
explicitly references that backend in a model ref or under
|
||||
`agents.defaults.cliBackends`.
|
||||
|
||||
## Using it as a fallback
|
||||
|
||||
Add a CLI backend to your fallback list so it only runs when primary models fail:
|
||||
|
||||
@@ -78,6 +78,7 @@ Those belong in your plugin code and `package.json`.
|
||||
"description": "OpenRouter provider plugin",
|
||||
"version": "1.0.0",
|
||||
"providers": ["openrouter"],
|
||||
"cliBackends": ["openrouter-cli"],
|
||||
"providerAuthEnvVars": {
|
||||
"openrouter": ["OPENROUTER_API_KEY"]
|
||||
},
|
||||
@@ -125,6 +126,7 @@ Those belong in your plugin code and `package.json`.
|
||||
| `kind` | No | `"memory"` \| `"context-engine"` | Declares an exclusive plugin kind used by `plugins.slots.*`. |
|
||||
| `channels` | No | `string[]` | Channel ids owned by this plugin. Used for discovery and config validation. |
|
||||
| `providers` | No | `string[]` | Provider ids owned by this plugin. |
|
||||
| `cliBackends` | No | `string[]` | CLI inference backend ids owned by this plugin. Used for startup auto-activation from explicit config refs. |
|
||||
| `providerAuthEnvVars` | No | `Record<string, string[]>` | Cheap provider-auth env metadata that OpenClaw can inspect without loading plugin code. |
|
||||
| `providerAuthChoices` | No | `object[]` | Cheap auth-choice metadata for onboarding pickers, preferred-provider resolution, and simple CLI flag wiring. |
|
||||
| `skills` | No | `string[]` | Skill directories to load, relative to the plugin root. |
|
||||
@@ -234,8 +236,8 @@ See [Configuration reference](/gateway/configuration) for the full `plugins.*` s
|
||||
- `kind: "memory"` is selected by `plugins.slots.memory`.
|
||||
- `kind: "context-engine"` is selected by `plugins.slots.contextEngine`
|
||||
(default: built-in `legacy`).
|
||||
- `channels`, `providers`, and `skills` can be omitted when a plugin does not
|
||||
need them.
|
||||
- `channels`, `providers`, `cliBackends`, and `skills` can be omitted when a
|
||||
plugin does not need them.
|
||||
- If your plugin depends on native modules, document the build steps and any
|
||||
package-manager allowlist requirements (for example, pnpm `allow-build-scripts`
|
||||
- `pnpm rebuild <package>`).
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
---
|
||||
summary: "Use Anthropic Claude via API keys or setup-token in OpenClaw"
|
||||
summary: "Use Anthropic Claude via API keys, setup-token, or Claude CLI in OpenClaw"
|
||||
read_when:
|
||||
- You want to use Anthropic models in OpenClaw
|
||||
- You want setup-token instead of API keys
|
||||
- You want to reuse Claude CLI subscription auth on the gateway host
|
||||
title: "Anthropic"
|
||||
---
|
||||
|
||||
@@ -186,7 +187,90 @@ Note: Anthropic currently rejects `context-1m-*` beta requests when using
|
||||
OAuth/subscription tokens (`sk-ant-oat-*`). OpenClaw automatically skips the
|
||||
context1m beta header for OAuth auth and keeps the required OAuth betas.
|
||||
|
||||
## Option B: Claude setup-token
|
||||
## Option B: Claude CLI as the message provider
|
||||
|
||||
**Best for:** a single-user gateway host that already has Claude CLI installed
|
||||
and signed in with a Claude subscription.
|
||||
|
||||
This path uses the local `claude` binary for model inference instead of calling
|
||||
the Anthropic API directly. OpenClaw treats it as a **CLI backend provider**
|
||||
with model refs like:
|
||||
|
||||
- `claude-cli/claude-sonnet-4-6`
|
||||
- `claude-cli/claude-opus-4-6`
|
||||
|
||||
How it works:
|
||||
|
||||
1. OpenClaw launches `claude -p --output-format json ...` on the **gateway
|
||||
host**.
|
||||
2. The first turn sends `--session-id <uuid>`.
|
||||
3. Follow-up turns reuse the stored Claude session via `--resume <sessionId>`.
|
||||
4. Your chat messages still go through the normal OpenClaw message pipeline, but
|
||||
the actual model reply is produced by Claude CLI.
|
||||
|
||||
### Requirements
|
||||
|
||||
- Claude CLI installed on the gateway host and available on PATH, or configured
|
||||
with an absolute command path.
|
||||
- Claude CLI already authenticated on that same host:
|
||||
|
||||
```bash
|
||||
claude auth status
|
||||
```
|
||||
|
||||
- OpenClaw auto-loads the bundled Anthropic plugin at gateway startup when your
|
||||
config explicitly references `claude-cli/...` or `claude-cli` backend config.
|
||||
|
||||
### Config snippet
|
||||
|
||||
```json5
|
||||
{
|
||||
agents: {
|
||||
defaults: {
|
||||
model: {
|
||||
primary: "claude-cli/claude-sonnet-4-6",
|
||||
},
|
||||
models: {
|
||||
"claude-cli/claude-sonnet-4-6": {},
|
||||
},
|
||||
sandbox: { mode: "off" },
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
If the `claude` binary is not on the gateway host PATH:
|
||||
|
||||
```json5
|
||||
{
|
||||
agents: {
|
||||
defaults: {
|
||||
cliBackends: {
|
||||
"claude-cli": {
|
||||
command: "/opt/homebrew/bin/claude",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
### What you get
|
||||
|
||||
- Claude subscription auth reused from the local CLI
|
||||
- Normal OpenClaw message/session routing
|
||||
- Claude CLI session continuity across turns
|
||||
|
||||
### Important limits
|
||||
|
||||
- This is **not** the Anthropic API provider. It is the local CLI runtime.
|
||||
- Tools are disabled on the OpenClaw side for CLI backend runs.
|
||||
- Text in, text out. No OpenClaw streaming handoff.
|
||||
- Best fit for a personal gateway host, not shared multi-user billing setups.
|
||||
|
||||
More details: [/gateway/cli-backends](/gateway/cli-backends)
|
||||
|
||||
## Option C: Claude setup-token
|
||||
|
||||
**Best for:** using your Claude subscription.
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
{
|
||||
"id": "anthropic",
|
||||
"providers": ["anthropic"],
|
||||
"cliBackends": ["claude-cli"],
|
||||
"providerAuthEnvVars": {
|
||||
"anthropic": ["ANTHROPIC_OAUTH_TOKEN", "ANTHROPIC_API_KEY"]
|
||||
},
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
{
|
||||
"id": "google",
|
||||
"providers": ["google", "google-gemini-cli"],
|
||||
"cliBackends": ["google-gemini-cli"],
|
||||
"providerAuthEnvVars": {
|
||||
"google": ["GEMINI_API_KEY", "GOOGLE_API_KEY"]
|
||||
},
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
{
|
||||
"id": "openai",
|
||||
"providers": ["openai", "openai-codex"],
|
||||
"cliBackends": ["codex-cli"],
|
||||
"providerAuthEnvVars": {
|
||||
"openai": ["OPENAI_API_KEY"]
|
||||
},
|
||||
|
||||
@@ -34,6 +34,7 @@ function buildRegistry(params: { acpxRoot: string; helperRoot: string }): Plugin
|
||||
name: "ACPX Runtime",
|
||||
channels: [],
|
||||
providers: [],
|
||||
cliBackends: [],
|
||||
skills: ["./skills"],
|
||||
hooks: [],
|
||||
origin: "workspace",
|
||||
@@ -46,6 +47,7 @@ function buildRegistry(params: { acpxRoot: string; helperRoot: string }): Plugin
|
||||
name: "Helper",
|
||||
channels: [],
|
||||
providers: [],
|
||||
cliBackends: [],
|
||||
skills: ["./skills"],
|
||||
hooks: [],
|
||||
origin: "workspace",
|
||||
@@ -71,6 +73,7 @@ function createSinglePluginRegistry(params: {
|
||||
format: params.format,
|
||||
channels: [],
|
||||
providers: [],
|
||||
cliBackends: [],
|
||||
skills: params.skills,
|
||||
hooks: [],
|
||||
origin: "workspace",
|
||||
|
||||
@@ -8,6 +8,7 @@ function manifest(id: string): PluginManifestRecord {
|
||||
id,
|
||||
channels: [],
|
||||
providers: [],
|
||||
cliBackends: [],
|
||||
skills: [],
|
||||
hooks: [],
|
||||
origin: "bundled",
|
||||
|
||||
@@ -13,6 +13,7 @@ function manifest(id: string): PluginManifestRecord {
|
||||
id,
|
||||
channels: [],
|
||||
providers: [],
|
||||
cliBackends: [],
|
||||
skills: [],
|
||||
hooks: [],
|
||||
origin: "bundled",
|
||||
|
||||
@@ -61,6 +61,7 @@ function makeRegistry(plugins: Array<{ id: string; channels: string[] }>): Plugi
|
||||
id: p.id,
|
||||
channels: p.channels,
|
||||
providers: [],
|
||||
cliBackends: [],
|
||||
skills: [],
|
||||
hooks: [],
|
||||
origin: "config" as const,
|
||||
|
||||
@@ -24,24 +24,40 @@ describe("resolveGatewayStartupPluginIds", () => {
|
||||
channels: ["discord"],
|
||||
origin: "bundled",
|
||||
enabledByDefault: undefined,
|
||||
providers: [],
|
||||
cliBackends: [],
|
||||
},
|
||||
{
|
||||
id: "amazon-bedrock",
|
||||
channels: [],
|
||||
origin: "bundled",
|
||||
enabledByDefault: true,
|
||||
providers: [],
|
||||
cliBackends: [],
|
||||
},
|
||||
{
|
||||
id: "anthropic",
|
||||
channels: [],
|
||||
origin: "bundled",
|
||||
enabledByDefault: undefined,
|
||||
providers: ["anthropic"],
|
||||
cliBackends: ["claude-cli"],
|
||||
},
|
||||
{
|
||||
id: "diagnostics-otel",
|
||||
channels: [],
|
||||
origin: "bundled",
|
||||
enabledByDefault: undefined,
|
||||
providers: [],
|
||||
cliBackends: [],
|
||||
},
|
||||
{
|
||||
id: "custom-sidecar",
|
||||
channels: [],
|
||||
origin: "global",
|
||||
enabledByDefault: undefined,
|
||||
providers: [],
|
||||
cliBackends: [],
|
||||
},
|
||||
],
|
||||
diagnostics: [],
|
||||
@@ -55,6 +71,14 @@ describe("resolveGatewayStartupPluginIds", () => {
|
||||
"diagnostics-otel": { enabled: true },
|
||||
},
|
||||
},
|
||||
agents: {
|
||||
defaults: {
|
||||
model: { primary: "claude-cli/claude-sonnet-4-6" },
|
||||
models: {
|
||||
"claude-cli/claude-sonnet-4-6": {},
|
||||
},
|
||||
},
|
||||
},
|
||||
} as OpenClawConfig;
|
||||
|
||||
expect(
|
||||
@@ -63,7 +87,7 @@ describe("resolveGatewayStartupPluginIds", () => {
|
||||
workspaceDir: "/tmp",
|
||||
env: process.env,
|
||||
}),
|
||||
).toEqual(["discord", "diagnostics-otel", "custom-sidecar"]);
|
||||
).toEqual(["discord", "anthropic", "diagnostics-otel", "custom-sidecar"]);
|
||||
});
|
||||
|
||||
it("does not pull default-on bundled non-channel plugins into startup", () => {
|
||||
@@ -77,4 +101,25 @@ describe("resolveGatewayStartupPluginIds", () => {
|
||||
}),
|
||||
).toEqual(["discord", "custom-sidecar"]);
|
||||
});
|
||||
|
||||
it("auto-loads bundled plugins referenced by configured provider ids", () => {
|
||||
const config = {
|
||||
models: {
|
||||
providers: {
|
||||
anthropic: {
|
||||
baseUrl: "https://example.com",
|
||||
models: [],
|
||||
},
|
||||
},
|
||||
},
|
||||
} as OpenClawConfig;
|
||||
|
||||
expect(
|
||||
resolveGatewayStartupPluginIds({
|
||||
config,
|
||||
workspaceDir: "/tmp",
|
||||
env: process.env,
|
||||
}),
|
||||
).toEqual(["discord", "anthropic", "custom-sidecar"]);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,8 +1,240 @@
|
||||
import { DEFAULT_PROVIDER } from "../agents/defaults.js";
|
||||
import {
|
||||
buildModelAliasIndex,
|
||||
normalizeProviderId,
|
||||
resolveModelRefFromString,
|
||||
} from "../agents/model-selection.js";
|
||||
import { listPotentialConfiguredChannelIds } from "../channels/config-presence.js";
|
||||
import type { OpenClawConfig } from "../config/config.js";
|
||||
import {
|
||||
resolveAgentModelFallbackValues,
|
||||
resolveAgentModelPrimaryValue,
|
||||
} from "../config/model-input.js";
|
||||
import { normalizePluginsConfig, resolveEffectiveEnableState } from "./config-state.js";
|
||||
import { loadPluginManifestRegistry } from "./manifest-registry.js";
|
||||
|
||||
type ModelListLike = string | { primary?: string; fallbacks?: string[] } | undefined;
|
||||
|
||||
function addResolvedActivationId(params: {
|
||||
raw: string | undefined;
|
||||
activationIds: Set<string>;
|
||||
aliasIndex: ReturnType<typeof buildModelAliasIndex>;
|
||||
}): void {
|
||||
const raw = params.raw?.trim();
|
||||
if (!raw) {
|
||||
return;
|
||||
}
|
||||
const resolved = resolveModelRefFromString({
|
||||
raw,
|
||||
defaultProvider: DEFAULT_PROVIDER,
|
||||
aliasIndex: params.aliasIndex,
|
||||
});
|
||||
if (!resolved) {
|
||||
return;
|
||||
}
|
||||
params.activationIds.add(normalizeProviderId(resolved.ref.provider));
|
||||
}
|
||||
|
||||
function addModelListActivationIds(params: {
|
||||
value: ModelListLike;
|
||||
activationIds: Set<string>;
|
||||
aliasIndex: ReturnType<typeof buildModelAliasIndex>;
|
||||
}): void {
|
||||
addResolvedActivationId({
|
||||
raw: resolveAgentModelPrimaryValue(params.value),
|
||||
activationIds: params.activationIds,
|
||||
aliasIndex: params.aliasIndex,
|
||||
});
|
||||
for (const fallback of resolveAgentModelFallbackValues(params.value)) {
|
||||
addResolvedActivationId({
|
||||
raw: fallback,
|
||||
activationIds: params.activationIds,
|
||||
aliasIndex: params.aliasIndex,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function addProviderModelPairActivationId(params: {
|
||||
provider: string | undefined;
|
||||
model: string | undefined;
|
||||
activationIds: Set<string>;
|
||||
}): void {
|
||||
const provider = normalizeProviderId(params.provider ?? "");
|
||||
const model = params.model?.trim();
|
||||
if (!provider || !model) {
|
||||
return;
|
||||
}
|
||||
params.activationIds.add(provider);
|
||||
}
|
||||
|
||||
function collectConfiguredActivationIds(config: OpenClawConfig): Set<string> {
|
||||
const activationIds = new Set<string>();
|
||||
const aliasIndex = buildModelAliasIndex({
|
||||
cfg: config,
|
||||
defaultProvider: DEFAULT_PROVIDER,
|
||||
});
|
||||
|
||||
addModelListActivationIds({ value: config.agents?.defaults?.model, activationIds, aliasIndex });
|
||||
addModelListActivationIds({
|
||||
value: config.agents?.defaults?.imageModel,
|
||||
activationIds,
|
||||
aliasIndex,
|
||||
});
|
||||
addModelListActivationIds({
|
||||
value: config.agents?.defaults?.imageGenerationModel,
|
||||
activationIds,
|
||||
aliasIndex,
|
||||
});
|
||||
addModelListActivationIds({
|
||||
value: config.agents?.defaults?.pdfModel,
|
||||
activationIds,
|
||||
aliasIndex,
|
||||
});
|
||||
addResolvedActivationId({
|
||||
raw: config.agents?.defaults?.compaction?.model,
|
||||
activationIds,
|
||||
aliasIndex,
|
||||
});
|
||||
addResolvedActivationId({
|
||||
raw: config.agents?.defaults?.heartbeat?.model,
|
||||
activationIds,
|
||||
aliasIndex,
|
||||
});
|
||||
addModelListActivationIds({
|
||||
value: config.agents?.defaults?.subagents?.model,
|
||||
activationIds,
|
||||
aliasIndex,
|
||||
});
|
||||
addResolvedActivationId({
|
||||
raw: config.messages?.tts?.summaryModel,
|
||||
activationIds,
|
||||
aliasIndex,
|
||||
});
|
||||
addResolvedActivationId({
|
||||
raw: config.hooks?.gmail?.model,
|
||||
activationIds,
|
||||
aliasIndex,
|
||||
});
|
||||
|
||||
for (const modelRef of Object.keys(config.agents?.defaults?.models ?? {})) {
|
||||
addResolvedActivationId({
|
||||
raw: modelRef,
|
||||
activationIds,
|
||||
aliasIndex,
|
||||
});
|
||||
}
|
||||
|
||||
for (const providerId of Object.keys(config.agents?.defaults?.cliBackends ?? {})) {
|
||||
const normalized = normalizeProviderId(providerId);
|
||||
if (normalized) {
|
||||
activationIds.add(normalized);
|
||||
}
|
||||
}
|
||||
|
||||
for (const providerId of Object.keys(config.models?.providers ?? {})) {
|
||||
const normalized = normalizeProviderId(providerId);
|
||||
if (normalized) {
|
||||
activationIds.add(normalized);
|
||||
}
|
||||
}
|
||||
|
||||
for (const agent of config.agents?.list ?? []) {
|
||||
addModelListActivationIds({ value: agent.model, activationIds, aliasIndex });
|
||||
addModelListActivationIds({ value: agent.subagents?.model, activationIds, aliasIndex });
|
||||
addResolvedActivationId({
|
||||
raw: agent.heartbeat?.model,
|
||||
activationIds,
|
||||
aliasIndex,
|
||||
});
|
||||
}
|
||||
|
||||
for (const mapping of config.hooks?.mappings ?? []) {
|
||||
addResolvedActivationId({
|
||||
raw: mapping.model,
|
||||
activationIds,
|
||||
aliasIndex,
|
||||
});
|
||||
}
|
||||
|
||||
for (const channelMap of Object.values(config.channels?.modelByChannel ?? {})) {
|
||||
if (!channelMap || typeof channelMap !== "object") {
|
||||
continue;
|
||||
}
|
||||
for (const raw of Object.values(channelMap)) {
|
||||
addResolvedActivationId({
|
||||
raw: typeof raw === "string" ? raw : undefined,
|
||||
activationIds,
|
||||
aliasIndex,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
addResolvedActivationId({
|
||||
raw: config.tools?.subagents?.model
|
||||
? resolveAgentModelPrimaryValue(config.tools?.subagents?.model)
|
||||
: undefined,
|
||||
activationIds,
|
||||
aliasIndex,
|
||||
});
|
||||
if (config.tools?.subagents?.model) {
|
||||
for (const fallback of resolveAgentModelFallbackValues(config.tools.subagents.model)) {
|
||||
addResolvedActivationId({ raw: fallback, activationIds, aliasIndex });
|
||||
}
|
||||
}
|
||||
|
||||
addResolvedActivationId({
|
||||
raw: config.tools?.web?.search?.gemini?.model,
|
||||
activationIds,
|
||||
aliasIndex,
|
||||
});
|
||||
addResolvedActivationId({
|
||||
raw: config.tools?.web?.search?.grok?.model,
|
||||
activationIds,
|
||||
aliasIndex,
|
||||
});
|
||||
addResolvedActivationId({
|
||||
raw: config.tools?.web?.search?.kimi?.model,
|
||||
activationIds,
|
||||
aliasIndex,
|
||||
});
|
||||
addResolvedActivationId({
|
||||
raw: config.tools?.web?.search?.perplexity?.model,
|
||||
activationIds,
|
||||
aliasIndex,
|
||||
});
|
||||
|
||||
for (const entry of config.tools?.media?.models ?? []) {
|
||||
addProviderModelPairActivationId({
|
||||
provider: entry.provider,
|
||||
model: entry.model,
|
||||
activationIds,
|
||||
});
|
||||
}
|
||||
for (const entry of config.tools?.media?.image?.models ?? []) {
|
||||
addProviderModelPairActivationId({
|
||||
provider: entry.provider,
|
||||
model: entry.model,
|
||||
activationIds,
|
||||
});
|
||||
}
|
||||
for (const entry of config.tools?.media?.audio?.models ?? []) {
|
||||
addProviderModelPairActivationId({
|
||||
provider: entry.provider,
|
||||
model: entry.model,
|
||||
activationIds,
|
||||
});
|
||||
}
|
||||
for (const entry of config.tools?.media?.video?.models ?? []) {
|
||||
addProviderModelPairActivationId({
|
||||
provider: entry.provider,
|
||||
model: entry.model,
|
||||
activationIds,
|
||||
});
|
||||
}
|
||||
|
||||
return activationIds;
|
||||
}
|
||||
|
||||
export function resolveChannelPluginIds(params: {
|
||||
config: OpenClawConfig;
|
||||
workspaceDir?: string;
|
||||
@@ -69,6 +301,7 @@ export function resolveGatewayStartupPluginIds(params: {
|
||||
workspaceDir: params.workspaceDir,
|
||||
env: params.env,
|
||||
});
|
||||
const configuredActivationIds = collectConfiguredActivationIds(params.config);
|
||||
return manifestRegistry.plugins
|
||||
.filter((plugin) => {
|
||||
if (plugin.channels.some((channelId) => configuredChannelIds.has(channelId))) {
|
||||
@@ -90,6 +323,16 @@ export function resolveGatewayStartupPluginIds(params: {
|
||||
if (plugin.origin !== "bundled") {
|
||||
return true;
|
||||
}
|
||||
if (
|
||||
plugin.providers.some((providerId) =>
|
||||
configuredActivationIds.has(normalizeProviderId(providerId)),
|
||||
) ||
|
||||
plugin.cliBackends.some((backendId) =>
|
||||
configuredActivationIds.has(normalizeProviderId(backendId)),
|
||||
)
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
return (
|
||||
pluginsConfig.allow.includes(plugin.id) ||
|
||||
pluginsConfig.entries[plugin.id]?.enabled === true ||
|
||||
|
||||
@@ -223,6 +223,7 @@ describe("loadPluginManifestRegistry", () => {
|
||||
id: "openai",
|
||||
enabledByDefault: true,
|
||||
providers: ["openai", "openai-codex"],
|
||||
cliBackends: ["codex-cli"],
|
||||
providerAuthEnvVars: {
|
||||
openai: ["OPENAI_API_KEY"],
|
||||
},
|
||||
@@ -246,6 +247,7 @@ describe("loadPluginManifestRegistry", () => {
|
||||
expect(registry.plugins[0]?.providerAuthEnvVars).toEqual({
|
||||
openai: ["OPENAI_API_KEY"],
|
||||
});
|
||||
expect(registry.plugins[0]?.cliBackends).toEqual(["codex-cli"]);
|
||||
expect(registry.plugins[0]?.enabledByDefault).toBe(true);
|
||||
expect(registry.plugins[0]?.providerAuthChoices).toEqual([
|
||||
{
|
||||
|
||||
@@ -45,6 +45,7 @@ export type PluginManifestRecord = {
|
||||
kind?: PluginKind;
|
||||
channels: string[];
|
||||
providers: string[];
|
||||
cliBackends: string[];
|
||||
providerAuthEnvVars?: Record<string, string[]>;
|
||||
providerAuthChoices?: PluginManifest["providerAuthChoices"];
|
||||
skills: string[];
|
||||
@@ -170,6 +171,7 @@ function buildRecord(params: {
|
||||
kind: params.manifest.kind,
|
||||
channels: params.manifest.channels ?? [],
|
||||
providers: params.manifest.providers ?? [],
|
||||
cliBackends: params.manifest.cliBackends ?? [],
|
||||
providerAuthEnvVars: params.manifest.providerAuthEnvVars,
|
||||
providerAuthChoices: params.manifest.providerAuthChoices,
|
||||
skills: params.manifest.skills ?? [],
|
||||
@@ -224,6 +226,7 @@ function buildBundleRecord(params: {
|
||||
bundleCapabilities: params.manifest.capabilities,
|
||||
channels: [],
|
||||
providers: [],
|
||||
cliBackends: [],
|
||||
skills: params.manifest.skills ?? [],
|
||||
settingsFiles: params.manifest.settingsFiles ?? [],
|
||||
hooks: params.manifest.hooks ?? [],
|
||||
|
||||
@@ -15,6 +15,8 @@ export type PluginManifest = {
|
||||
kind?: PluginKind;
|
||||
channels?: string[];
|
||||
providers?: string[];
|
||||
/** Cheap startup activation lookup for plugin-owned CLI inference backends. */
|
||||
cliBackends?: string[];
|
||||
/** Cheap provider-auth env lookup without booting plugin runtime. */
|
||||
providerAuthEnvVars?: Record<string, string[]>;
|
||||
/**
|
||||
@@ -203,6 +205,7 @@ export function loadPluginManifest(
|
||||
const version = typeof raw.version === "string" ? raw.version.trim() : undefined;
|
||||
const channels = normalizeStringList(raw.channels);
|
||||
const providers = normalizeStringList(raw.providers);
|
||||
const cliBackends = normalizeStringList(raw.cliBackends);
|
||||
const providerAuthEnvVars = normalizeStringListRecord(raw.providerAuthEnvVars);
|
||||
const providerAuthChoices = normalizeProviderAuthChoices(raw.providerAuthChoices);
|
||||
const skills = normalizeStringList(raw.skills);
|
||||
@@ -221,6 +224,7 @@ export function loadPluginManifest(
|
||||
kind,
|
||||
channels,
|
||||
providers,
|
||||
cliBackends,
|
||||
providerAuthEnvVars,
|
||||
providerAuthChoices,
|
||||
skills,
|
||||
|
||||
Reference in New Issue
Block a user