refactor: extract single-provider plugin entry helper

This commit is contained in:
Peter Steinberger 2026-03-23 01:34:24 +00:00
parent 6237cfc6a6
commit 956fe72b39
15 changed files with 739 additions and 597 deletions

View File

@ -148,6 +148,43 @@ API key auth, and dynamic model resolution.
`openclaw onboard --acme-ai-api-key <key>` and select
`acme-ai/acme-large` as their model.
For bundled providers that only register one text provider with API-key
auth plus a single catalog-backed runtime, prefer the narrower
`defineSingleProviderPluginEntry(...)` helper:
```typescript
import { defineSingleProviderPluginEntry } from "openclaw/plugin-sdk/provider-entry";
export default defineSingleProviderPluginEntry({
id: "acme-ai",
name: "Acme AI",
description: "Acme AI model provider",
provider: {
label: "Acme AI",
docsPath: "/providers/acme-ai",
auth: [
{
methodId: "api-key",
label: "Acme AI API key",
hint: "API key from your Acme AI dashboard",
optionKey: "acmeAiApiKey",
flagName: "--acme-ai-api-key",
envVar: "ACME_AI_API_KEY",
promptMessage: "Enter your Acme AI API key",
defaultModel: "acme-ai/acme-large",
},
],
catalog: {
buildProvider: () => ({
api: "openai-completions",
baseUrl: "https://api.acme-ai.com/v1",
models: [{ id: "acme-large", name: "Acme Large" }],
}),
},
},
});
```
</Step>
<Step title="Add dynamic model resolution">

View File

@ -1,6 +1,4 @@
import { definePluginEntry } from "openclaw/plugin-sdk/plugin-entry";
import { createProviderApiKeyAuthMethod } from "openclaw/plugin-sdk/provider-auth";
import { buildSingleProviderApiKeyCatalog } from "openclaw/plugin-sdk/provider-catalog";
import { defineSingleProviderPluginEntry } from "openclaw/plugin-sdk/provider-entry";
import {
createKilocodeWrapper,
isProxyReasoningUnsupported,
@ -10,59 +8,40 @@ import { buildKilocodeProviderWithDiscovery } from "./provider-catalog.js";
const PROVIDER_ID = "kilocode";
export default definePluginEntry({
export default defineSingleProviderPluginEntry({
id: PROVIDER_ID,
name: "Kilo Gateway Provider",
description: "Bundled Kilo Gateway provider plugin",
register(api) {
api.registerProvider({
id: PROVIDER_ID,
label: "Kilo Gateway",
docsPath: "/providers/kilocode",
envVars: ["KILOCODE_API_KEY"],
auth: [
createProviderApiKeyAuthMethod({
providerId: PROVIDER_ID,
methodId: "api-key",
label: "Kilo Gateway API key",
hint: "API key (OpenRouter-compatible)",
optionKey: "kilocodeApiKey",
flagName: "--kilocode-api-key",
envVar: "KILOCODE_API_KEY",
promptMessage: "Enter Kilo Gateway API key",
defaultModel: KILOCODE_DEFAULT_MODEL_REF,
expectedProviders: ["kilocode"],
applyConfig: (cfg) => applyKilocodeConfig(cfg),
wizard: {
choiceId: "kilocode-api-key",
choiceLabel: "Kilo Gateway API key",
groupId: "kilocode",
groupLabel: "Kilo Gateway",
groupHint: "API key (OpenRouter-compatible)",
},
}),
],
catalog: {
order: "simple",
run: (ctx) =>
buildSingleProviderApiKeyCatalog({
ctx,
providerId: PROVIDER_ID,
buildProvider: buildKilocodeProviderWithDiscovery,
}),
provider: {
label: "Kilo Gateway",
docsPath: "/providers/kilocode",
auth: [
{
methodId: "api-key",
label: "Kilo Gateway API key",
hint: "API key (OpenRouter-compatible)",
optionKey: "kilocodeApiKey",
flagName: "--kilocode-api-key",
envVar: "KILOCODE_API_KEY",
promptMessage: "Enter Kilo Gateway API key",
defaultModel: KILOCODE_DEFAULT_MODEL_REF,
applyConfig: (cfg) => applyKilocodeConfig(cfg),
},
capabilities: {
geminiThoughtSignatureSanitization: true,
geminiThoughtSignatureModelHints: ["gemini"],
},
wrapStreamFn: (ctx) => {
const thinkingLevel =
ctx.modelId === "kilo/auto" || isProxyReasoningUnsupported(ctx.modelId)
? undefined
: ctx.thinkingLevel;
return createKilocodeWrapper(ctx.streamFn, thinkingLevel);
},
isCacheTtlEligible: (ctx) => ctx.modelId.startsWith("anthropic/"),
});
],
catalog: {
buildProvider: buildKilocodeProviderWithDiscovery,
},
capabilities: {
geminiThoughtSignatureSanitization: true,
geminiThoughtSignatureModelHints: ["gemini"],
},
wrapStreamFn: (ctx) => {
const thinkingLevel =
ctx.modelId === "kilo/auto" || isProxyReasoningUnsupported(ctx.modelId)
? undefined
: ctx.thinkingLevel;
return createKilocodeWrapper(ctx.streamFn, thinkingLevel);
},
isCacheTtlEligible: (ctx) => ctx.modelId.startsWith("anthropic/"),
},
});

View File

@ -1,67 +1,51 @@
import { definePluginEntry } from "openclaw/plugin-sdk/plugin-entry";
import { createProviderApiKeyAuthMethod } from "openclaw/plugin-sdk/provider-auth";
import { buildSingleProviderApiKeyCatalog } from "openclaw/plugin-sdk/provider-catalog";
import { defineSingleProviderPluginEntry } from "openclaw/plugin-sdk/provider-entry";
import { mistralMediaUnderstandingProvider } from "./media-understanding-provider.js";
import { applyMistralConfig, MISTRAL_DEFAULT_MODEL_REF } from "./onboard.js";
import { buildMistralProvider } from "./provider-catalog.js";
const PROVIDER_ID = "mistral";
export default definePluginEntry({
export default defineSingleProviderPluginEntry({
id: PROVIDER_ID,
name: "Mistral Provider",
description: "Bundled Mistral provider plugin",
register(api) {
api.registerProvider({
id: PROVIDER_ID,
label: "Mistral",
docsPath: "/providers/models",
envVars: ["MISTRAL_API_KEY"],
auth: [
createProviderApiKeyAuthMethod({
providerId: PROVIDER_ID,
methodId: "api-key",
label: "Mistral API key",
hint: "API key",
optionKey: "mistralApiKey",
flagName: "--mistral-api-key",
envVar: "MISTRAL_API_KEY",
promptMessage: "Enter Mistral API key",
defaultModel: MISTRAL_DEFAULT_MODEL_REF,
expectedProviders: ["mistral"],
applyConfig: (cfg) => applyMistralConfig(cfg),
wizard: {
choiceId: "mistral-api-key",
choiceLabel: "Mistral API key",
groupId: "mistral",
groupLabel: "Mistral AI",
groupHint: "API key",
},
}),
provider: {
label: "Mistral",
docsPath: "/providers/models",
auth: [
{
methodId: "api-key",
label: "Mistral API key",
hint: "API key",
optionKey: "mistralApiKey",
flagName: "--mistral-api-key",
envVar: "MISTRAL_API_KEY",
promptMessage: "Enter Mistral API key",
defaultModel: MISTRAL_DEFAULT_MODEL_REF,
applyConfig: (cfg) => applyMistralConfig(cfg),
wizard: {
groupLabel: "Mistral AI",
},
},
],
catalog: {
buildProvider: buildMistralProvider,
allowExplicitBaseUrl: true,
},
capabilities: {
transcriptToolCallIdMode: "strict9",
transcriptToolCallIdModelHints: [
"mistral",
"mixtral",
"codestral",
"pixtral",
"devstral",
"ministral",
"mistralai",
],
catalog: {
order: "simple",
run: (ctx) =>
buildSingleProviderApiKeyCatalog({
ctx,
providerId: PROVIDER_ID,
buildProvider: buildMistralProvider,
allowExplicitBaseUrl: true,
}),
},
capabilities: {
transcriptToolCallIdMode: "strict9",
transcriptToolCallIdModelHints: [
"mistral",
"mixtral",
"codestral",
"pixtral",
"devstral",
"ministral",
"mistralai",
],
},
});
},
},
register(api) {
api.registerMediaUnderstandingProvider(mistralMediaUnderstandingProvider);
},
});

View File

@ -1,6 +1,4 @@
import { definePluginEntry } from "openclaw/plugin-sdk/plugin-entry";
import { createProviderApiKeyAuthMethod } from "openclaw/plugin-sdk/provider-auth";
import { buildSingleProviderApiKeyCatalog } from "openclaw/plugin-sdk/provider-catalog";
import { defineSingleProviderPluginEntry } from "openclaw/plugin-sdk/provider-entry";
import {
applyModelStudioConfig,
applyModelStudioConfigCn,
@ -10,82 +8,62 @@ import { buildModelStudioProvider } from "./provider-catalog.js";
const PROVIDER_ID = "modelstudio";
export default definePluginEntry({
export default defineSingleProviderPluginEntry({
id: PROVIDER_ID,
name: "Model Studio Provider",
description: "Bundled Model Studio provider plugin",
register(api) {
api.registerProvider({
id: PROVIDER_ID,
label: "Model Studio",
docsPath: "/providers/models",
envVars: ["MODELSTUDIO_API_KEY"],
auth: [
createProviderApiKeyAuthMethod({
providerId: PROVIDER_ID,
methodId: "api-key-cn",
label: "Coding Plan API Key for China (subscription)",
hint: "Endpoint: coding.dashscope.aliyuncs.com",
optionKey: "modelstudioApiKeyCn",
flagName: "--modelstudio-api-key-cn",
envVar: "MODELSTUDIO_API_KEY",
promptMessage: "Enter Alibaba Cloud Model Studio Coding Plan API key (China)",
defaultModel: MODELSTUDIO_DEFAULT_MODEL_REF,
expectedProviders: ["modelstudio"],
applyConfig: (cfg) => applyModelStudioConfigCn(cfg),
noteMessage: [
"Get your API key at: https://bailian.console.aliyun.com/",
"Endpoint: coding.dashscope.aliyuncs.com",
"Models: qwen3.5-plus, glm-4.7, kimi-k2.5, MiniMax-M2.5, etc.",
].join("\n"),
noteTitle: "Alibaba Cloud Model Studio Coding Plan (China)",
wizard: {
choiceId: "modelstudio-api-key-cn",
choiceLabel: "Coding Plan API Key for China (subscription)",
choiceHint: "Endpoint: coding.dashscope.aliyuncs.com",
groupId: "modelstudio",
groupLabel: "Alibaba Cloud Model Studio",
groupHint: "Coding Plan API key (CN / Global)",
},
}),
createProviderApiKeyAuthMethod({
providerId: PROVIDER_ID,
methodId: "api-key",
label: "Coding Plan API Key for Global/Intl (subscription)",
hint: "Endpoint: coding-intl.dashscope.aliyuncs.com",
optionKey: "modelstudioApiKey",
flagName: "--modelstudio-api-key",
envVar: "MODELSTUDIO_API_KEY",
promptMessage: "Enter Alibaba Cloud Model Studio Coding Plan API key (Global/Intl)",
defaultModel: MODELSTUDIO_DEFAULT_MODEL_REF,
expectedProviders: ["modelstudio"],
applyConfig: (cfg) => applyModelStudioConfig(cfg),
noteMessage: [
"Get your API key at: https://bailian.console.aliyun.com/",
"Endpoint: coding-intl.dashscope.aliyuncs.com",
"Models: qwen3.5-plus, glm-4.7, kimi-k2.5, MiniMax-M2.5, etc.",
].join("\n"),
noteTitle: "Alibaba Cloud Model Studio Coding Plan (Global/Intl)",
wizard: {
choiceId: "modelstudio-api-key",
choiceLabel: "Coding Plan API Key for Global/Intl (subscription)",
choiceHint: "Endpoint: coding-intl.dashscope.aliyuncs.com",
groupId: "modelstudio",
groupLabel: "Alibaba Cloud Model Studio",
groupHint: "Coding Plan API key (CN / Global)",
},
}),
],
catalog: {
order: "simple",
run: (ctx) =>
buildSingleProviderApiKeyCatalog({
ctx,
providerId: PROVIDER_ID,
buildProvider: buildModelStudioProvider,
allowExplicitBaseUrl: true,
}),
provider: {
label: "Model Studio",
docsPath: "/providers/models",
auth: [
{
methodId: "api-key-cn",
label: "Coding Plan API Key for China (subscription)",
hint: "Endpoint: coding.dashscope.aliyuncs.com",
optionKey: "modelstudioApiKeyCn",
flagName: "--modelstudio-api-key-cn",
envVar: "MODELSTUDIO_API_KEY",
promptMessage: "Enter Alibaba Cloud Model Studio Coding Plan API key (China)",
defaultModel: MODELSTUDIO_DEFAULT_MODEL_REF,
applyConfig: (cfg) => applyModelStudioConfigCn(cfg),
noteMessage: [
"Get your API key at: https://bailian.console.aliyun.com/",
"Endpoint: coding.dashscope.aliyuncs.com",
"Models: qwen3.5-plus, glm-4.7, kimi-k2.5, MiniMax-M2.5, etc.",
].join("\n"),
noteTitle: "Alibaba Cloud Model Studio Coding Plan (China)",
wizard: {
choiceHint: "Endpoint: coding.dashscope.aliyuncs.com",
groupLabel: "Alibaba Cloud Model Studio",
groupHint: "Coding Plan API key (CN / Global)",
},
},
});
{
methodId: "api-key",
label: "Coding Plan API Key for Global/Intl (subscription)",
hint: "Endpoint: coding-intl.dashscope.aliyuncs.com",
optionKey: "modelstudioApiKey",
flagName: "--modelstudio-api-key",
envVar: "MODELSTUDIO_API_KEY",
promptMessage: "Enter Alibaba Cloud Model Studio Coding Plan API key (Global/Intl)",
defaultModel: MODELSTUDIO_DEFAULT_MODEL_REF,
applyConfig: (cfg) => applyModelStudioConfig(cfg),
noteMessage: [
"Get your API key at: https://bailian.console.aliyun.com/",
"Endpoint: coding-intl.dashscope.aliyuncs.com",
"Models: qwen3.5-plus, glm-4.7, kimi-k2.5, MiniMax-M2.5, etc.",
].join("\n"),
noteTitle: "Alibaba Cloud Model Studio Coding Plan (Global/Intl)",
wizard: {
choiceHint: "Endpoint: coding-intl.dashscope.aliyuncs.com",
groupLabel: "Alibaba Cloud Model Studio",
groupHint: "Coding Plan API key (CN / Global)",
},
},
],
catalog: {
buildProvider: buildModelStudioProvider,
allowExplicitBaseUrl: true,
},
},
});

View File

@ -1,6 +1,4 @@
import { definePluginEntry } from "openclaw/plugin-sdk/plugin-entry";
import { createProviderApiKeyAuthMethod } from "openclaw/plugin-sdk/provider-auth";
import { buildSingleProviderApiKeyCatalog } from "openclaw/plugin-sdk/provider-catalog";
import { defineSingleProviderPluginEntry } from "openclaw/plugin-sdk/provider-entry";
import {
createMoonshotThinkingWrapper,
resolveMoonshotThinkingType,
@ -16,76 +14,56 @@ import { createKimiWebSearchProvider } from "./src/kimi-web-search-provider.js";
const PROVIDER_ID = "moonshot";
export default definePluginEntry({
export default defineSingleProviderPluginEntry({
id: PROVIDER_ID,
name: "Moonshot Provider",
description: "Bundled Moonshot provider plugin",
provider: {
label: "Moonshot",
docsPath: "/providers/moonshot",
auth: [
{
methodId: "api-key",
label: "Kimi API key (.ai)",
hint: "Kimi K2.5 + Kimi",
optionKey: "moonshotApiKey",
flagName: "--moonshot-api-key",
envVar: "MOONSHOT_API_KEY",
promptMessage: "Enter Moonshot API key",
defaultModel: MOONSHOT_DEFAULT_MODEL_REF,
applyConfig: (cfg) => applyMoonshotConfig(cfg),
wizard: {
groupLabel: "Moonshot AI (Kimi K2.5)",
},
},
{
methodId: "api-key-cn",
label: "Kimi API key (.cn)",
hint: "Kimi K2.5 + Kimi",
optionKey: "moonshotApiKey",
flagName: "--moonshot-api-key",
envVar: "MOONSHOT_API_KEY",
promptMessage: "Enter Moonshot API key (.cn)",
defaultModel: MOONSHOT_DEFAULT_MODEL_REF,
applyConfig: (cfg) => applyMoonshotConfigCn(cfg),
wizard: {
groupLabel: "Moonshot AI (Kimi K2.5)",
},
},
],
catalog: {
buildProvider: buildMoonshotProvider,
allowExplicitBaseUrl: true,
},
wrapStreamFn: (ctx) => {
const thinkingType = resolveMoonshotThinkingType({
configuredThinking: ctx.extraParams?.thinking,
thinkingLevel: ctx.thinkingLevel,
});
return createMoonshotThinkingWrapper(ctx.streamFn, thinkingType);
},
},
register(api) {
api.registerProvider({
id: PROVIDER_ID,
label: "Moonshot",
docsPath: "/providers/moonshot",
envVars: ["MOONSHOT_API_KEY"],
auth: [
createProviderApiKeyAuthMethod({
providerId: PROVIDER_ID,
methodId: "api-key",
label: "Kimi API key (.ai)",
hint: "Kimi K2.5 + Kimi",
optionKey: "moonshotApiKey",
flagName: "--moonshot-api-key",
envVar: "MOONSHOT_API_KEY",
promptMessage: "Enter Moonshot API key",
defaultModel: MOONSHOT_DEFAULT_MODEL_REF,
expectedProviders: ["moonshot"],
applyConfig: (cfg) => applyMoonshotConfig(cfg),
wizard: {
choiceId: "moonshot-api-key",
choiceLabel: "Kimi API key (.ai)",
groupId: "moonshot",
groupLabel: "Moonshot AI (Kimi K2.5)",
groupHint: "Kimi K2.5 + Kimi",
},
}),
createProviderApiKeyAuthMethod({
providerId: PROVIDER_ID,
methodId: "api-key-cn",
label: "Kimi API key (.cn)",
hint: "Kimi K2.5 + Kimi",
optionKey: "moonshotApiKey",
flagName: "--moonshot-api-key",
envVar: "MOONSHOT_API_KEY",
promptMessage: "Enter Moonshot API key (.cn)",
defaultModel: MOONSHOT_DEFAULT_MODEL_REF,
expectedProviders: ["moonshot"],
applyConfig: (cfg) => applyMoonshotConfigCn(cfg),
wizard: {
choiceId: "moonshot-api-key-cn",
choiceLabel: "Kimi API key (.cn)",
groupId: "moonshot",
groupLabel: "Moonshot AI (Kimi K2.5)",
groupHint: "Kimi K2.5 + Kimi",
},
}),
],
catalog: {
order: "simple",
run: (ctx) =>
buildSingleProviderApiKeyCatalog({
ctx,
providerId: PROVIDER_ID,
buildProvider: buildMoonshotProvider,
allowExplicitBaseUrl: true,
}),
},
wrapStreamFn: (ctx) => {
const thinkingType = resolveMoonshotThinkingType({
configuredThinking: ctx.extraParams?.thinking,
thinkingLevel: ctx.thinkingLevel,
});
return createMoonshotThinkingWrapper(ctx.streamFn, thinkingType);
},
});
api.registerMediaUnderstandingProvider(moonshotMediaUnderstandingProvider);
api.registerWebSearchProvider(createKimiWebSearchProvider());
},

View File

@ -1,29 +1,19 @@
import { definePluginEntry } from "openclaw/plugin-sdk/plugin-entry";
import { buildSingleProviderApiKeyCatalog } from "openclaw/plugin-sdk/provider-catalog";
import { defineSingleProviderPluginEntry } from "openclaw/plugin-sdk/provider-entry";
import { buildNvidiaProvider } from "./provider-catalog.js";
const PROVIDER_ID = "nvidia";
export default definePluginEntry({
export default defineSingleProviderPluginEntry({
id: PROVIDER_ID,
name: "NVIDIA Provider",
description: "Bundled NVIDIA provider plugin",
register(api) {
api.registerProvider({
id: PROVIDER_ID,
label: "NVIDIA",
docsPath: "/providers/nvidia",
envVars: ["NVIDIA_API_KEY"],
auth: [],
catalog: {
order: "simple",
run: (ctx) =>
buildSingleProviderApiKeyCatalog({
ctx,
providerId: PROVIDER_ID,
buildProvider: buildNvidiaProvider,
}),
},
});
provider: {
label: "NVIDIA",
docsPath: "/providers/nvidia",
envVars: ["NVIDIA_API_KEY"],
auth: [],
catalog: {
buildProvider: buildNvidiaProvider,
},
},
});

View File

@ -1,52 +1,31 @@
import { definePluginEntry } from "openclaw/plugin-sdk/plugin-entry";
import { createProviderApiKeyAuthMethod } from "openclaw/plugin-sdk/provider-auth";
import { buildSingleProviderApiKeyCatalog } from "openclaw/plugin-sdk/provider-catalog";
import { defineSingleProviderPluginEntry } from "openclaw/plugin-sdk/provider-entry";
import { applyQianfanConfig, QIANFAN_DEFAULT_MODEL_REF } from "./onboard.js";
import { buildQianfanProvider } from "./provider-catalog.js";
const PROVIDER_ID = "qianfan";
export default definePluginEntry({
export default defineSingleProviderPluginEntry({
id: PROVIDER_ID,
name: "Qianfan Provider",
description: "Bundled Qianfan provider plugin",
register(api) {
api.registerProvider({
id: PROVIDER_ID,
label: "Qianfan",
docsPath: "/providers/qianfan",
envVars: ["QIANFAN_API_KEY"],
auth: [
createProviderApiKeyAuthMethod({
providerId: PROVIDER_ID,
methodId: "api-key",
label: "Qianfan API key",
hint: "API key",
optionKey: "qianfanApiKey",
flagName: "--qianfan-api-key",
envVar: "QIANFAN_API_KEY",
promptMessage: "Enter Qianfan API key",
defaultModel: QIANFAN_DEFAULT_MODEL_REF,
expectedProviders: ["qianfan"],
applyConfig: (cfg) => applyQianfanConfig(cfg),
wizard: {
choiceId: "qianfan-api-key",
choiceLabel: "Qianfan API key",
groupId: "qianfan",
groupLabel: "Qianfan",
groupHint: "API key",
},
}),
],
catalog: {
order: "simple",
run: (ctx) =>
buildSingleProviderApiKeyCatalog({
ctx,
providerId: PROVIDER_ID,
buildProvider: buildQianfanProvider,
}),
provider: {
label: "Qianfan",
docsPath: "/providers/qianfan",
auth: [
{
methodId: "api-key",
label: "Qianfan API key",
hint: "API key",
optionKey: "qianfanApiKey",
flagName: "--qianfan-api-key",
envVar: "QIANFAN_API_KEY",
promptMessage: "Enter Qianfan API key",
defaultModel: QIANFAN_DEFAULT_MODEL_REF,
applyConfig: (cfg) => applyQianfanConfig(cfg),
},
});
],
catalog: {
buildProvider: buildQianfanProvider,
},
},
});

View File

@ -1,52 +1,31 @@
import { definePluginEntry } from "openclaw/plugin-sdk/plugin-entry";
import { createProviderApiKeyAuthMethod } from "openclaw/plugin-sdk/provider-auth";
import { buildSingleProviderApiKeyCatalog } from "openclaw/plugin-sdk/provider-catalog";
import { defineSingleProviderPluginEntry } from "openclaw/plugin-sdk/provider-entry";
import { applySyntheticConfig, SYNTHETIC_DEFAULT_MODEL_REF } from "./onboard.js";
import { buildSyntheticProvider } from "./provider-catalog.js";
const PROVIDER_ID = "synthetic";
export default definePluginEntry({
export default defineSingleProviderPluginEntry({
id: PROVIDER_ID,
name: "Synthetic Provider",
description: "Bundled Synthetic provider plugin",
register(api) {
api.registerProvider({
id: PROVIDER_ID,
label: "Synthetic",
docsPath: "/providers/synthetic",
envVars: ["SYNTHETIC_API_KEY"],
auth: [
createProviderApiKeyAuthMethod({
providerId: PROVIDER_ID,
methodId: "api-key",
label: "Synthetic API key",
hint: "Anthropic-compatible (multi-model)",
optionKey: "syntheticApiKey",
flagName: "--synthetic-api-key",
envVar: "SYNTHETIC_API_KEY",
promptMessage: "Enter Synthetic API key",
defaultModel: SYNTHETIC_DEFAULT_MODEL_REF,
expectedProviders: ["synthetic"],
applyConfig: (cfg) => applySyntheticConfig(cfg),
wizard: {
choiceId: "synthetic-api-key",
choiceLabel: "Synthetic API key",
groupId: "synthetic",
groupLabel: "Synthetic",
groupHint: "Anthropic-compatible (multi-model)",
},
}),
],
catalog: {
order: "simple",
run: (ctx) =>
buildSingleProviderApiKeyCatalog({
ctx,
providerId: PROVIDER_ID,
buildProvider: buildSyntheticProvider,
}),
provider: {
label: "Synthetic",
docsPath: "/providers/synthetic",
auth: [
{
methodId: "api-key",
label: "Synthetic API key",
hint: "Anthropic-compatible (multi-model)",
optionKey: "syntheticApiKey",
flagName: "--synthetic-api-key",
envVar: "SYNTHETIC_API_KEY",
promptMessage: "Enter Synthetic API key",
defaultModel: SYNTHETIC_DEFAULT_MODEL_REF,
applyConfig: (cfg) => applySyntheticConfig(cfg),
},
});
],
catalog: {
buildProvider: buildSyntheticProvider,
},
},
});

View File

@ -1,52 +1,34 @@
import { definePluginEntry } from "openclaw/plugin-sdk/plugin-entry";
import { createProviderApiKeyAuthMethod } from "openclaw/plugin-sdk/provider-auth";
import { buildSingleProviderApiKeyCatalog } from "openclaw/plugin-sdk/provider-catalog";
import { defineSingleProviderPluginEntry } from "openclaw/plugin-sdk/provider-entry";
import { applyTogetherConfig, TOGETHER_DEFAULT_MODEL_REF } from "./onboard.js";
import { buildTogetherProvider } from "./provider-catalog.js";
const PROVIDER_ID = "together";
export default definePluginEntry({
export default defineSingleProviderPluginEntry({
id: PROVIDER_ID,
name: "Together Provider",
description: "Bundled Together provider plugin",
register(api) {
api.registerProvider({
id: PROVIDER_ID,
label: "Together",
docsPath: "/providers/together",
envVars: ["TOGETHER_API_KEY"],
auth: [
createProviderApiKeyAuthMethod({
providerId: PROVIDER_ID,
methodId: "api-key",
label: "Together AI API key",
hint: "API key",
optionKey: "togetherApiKey",
flagName: "--together-api-key",
envVar: "TOGETHER_API_KEY",
promptMessage: "Enter Together AI API key",
defaultModel: TOGETHER_DEFAULT_MODEL_REF,
expectedProviders: ["together"],
applyConfig: (cfg) => applyTogetherConfig(cfg),
wizard: {
choiceId: "together-api-key",
choiceLabel: "Together AI API key",
groupId: "together",
groupLabel: "Together AI",
groupHint: "API key",
},
}),
],
catalog: {
order: "simple",
run: (ctx) =>
buildSingleProviderApiKeyCatalog({
ctx,
providerId: PROVIDER_ID,
buildProvider: buildTogetherProvider,
}),
provider: {
label: "Together",
docsPath: "/providers/together",
auth: [
{
methodId: "api-key",
label: "Together AI API key",
hint: "API key",
optionKey: "togetherApiKey",
flagName: "--together-api-key",
envVar: "TOGETHER_API_KEY",
promptMessage: "Enter Together AI API key",
defaultModel: TOGETHER_DEFAULT_MODEL_REF,
applyConfig: (cfg) => applyTogetherConfig(cfg),
wizard: {
groupLabel: "Together AI",
},
},
});
],
catalog: {
buildProvider: buildTogetherProvider,
},
},
});

View File

@ -1,6 +1,4 @@
import { definePluginEntry } from "openclaw/plugin-sdk/plugin-entry";
import { createProviderApiKeyAuthMethod } from "openclaw/plugin-sdk/provider-auth-api-key";
import { buildSingleProviderApiKeyCatalog } from "openclaw/plugin-sdk/provider-catalog";
import { defineSingleProviderPluginEntry } from "openclaw/plugin-sdk/provider-entry";
import { applyXaiModelCompat } from "openclaw/plugin-sdk/provider-models";
import { applyVeniceConfig, VENICE_DEFAULT_MODEL_REF } from "./onboard.js";
import { buildVeniceProvider } from "./provider-catalog.js";
@ -11,55 +9,39 @@ function isXaiBackedVeniceModel(modelId: string): boolean {
return modelId.trim().toLowerCase().includes("grok");
}
export default definePluginEntry({
export default defineSingleProviderPluginEntry({
id: PROVIDER_ID,
name: "Venice Provider",
description: "Bundled Venice provider plugin",
register(api) {
api.registerProvider({
id: PROVIDER_ID,
label: "Venice",
docsPath: "/providers/venice",
envVars: ["VENICE_API_KEY"],
auth: [
createProviderApiKeyAuthMethod({
providerId: PROVIDER_ID,
methodId: "api-key",
label: "Venice AI API key",
hint: "Privacy-focused (uncensored models)",
optionKey: "veniceApiKey",
flagName: "--venice-api-key",
envVar: "VENICE_API_KEY",
promptMessage: "Enter Venice AI API key",
defaultModel: VENICE_DEFAULT_MODEL_REF,
expectedProviders: ["venice"],
applyConfig: (cfg) => applyVeniceConfig(cfg),
noteMessage: [
"Venice AI provides privacy-focused inference with uncensored models.",
"Get your API key at: https://venice.ai/settings/api",
"Supports 'private' (fully private) and 'anonymized' (proxy) modes.",
].join("\n"),
noteTitle: "Venice AI",
wizard: {
choiceId: "venice-api-key",
choiceLabel: "Venice AI API key",
groupId: "venice",
groupLabel: "Venice AI",
groupHint: "Privacy-focused (uncensored models)",
},
}),
],
catalog: {
order: "simple",
run: (ctx) =>
buildSingleProviderApiKeyCatalog({
ctx,
providerId: PROVIDER_ID,
buildProvider: buildVeniceProvider,
}),
provider: {
label: "Venice",
docsPath: "/providers/venice",
auth: [
{
methodId: "api-key",
label: "Venice AI API key",
hint: "Privacy-focused (uncensored models)",
optionKey: "veniceApiKey",
flagName: "--venice-api-key",
envVar: "VENICE_API_KEY",
promptMessage: "Enter Venice AI API key",
defaultModel: VENICE_DEFAULT_MODEL_REF,
applyConfig: (cfg) => applyVeniceConfig(cfg),
noteMessage: [
"Venice AI provides privacy-focused inference with uncensored models.",
"Get your API key at: https://venice.ai/settings/api",
"Supports 'private' (fully private) and 'anonymized' (proxy) modes.",
].join("\n"),
noteTitle: "Venice AI",
wizard: {
groupLabel: "Venice AI",
},
},
normalizeResolvedModel: ({ modelId, model }) =>
isXaiBackedVeniceModel(modelId) ? applyXaiModelCompat(model) : undefined,
});
],
catalog: {
buildProvider: buildVeniceProvider,
},
normalizeResolvedModel: ({ modelId, model }) =>
isXaiBackedVeniceModel(modelId) ? applyXaiModelCompat(model) : undefined,
},
});

View File

@ -1,52 +1,35 @@
import { definePluginEntry } from "openclaw/plugin-sdk/plugin-entry";
import { createProviderApiKeyAuthMethod } from "openclaw/plugin-sdk/provider-auth";
import { buildSingleProviderApiKeyCatalog } from "openclaw/plugin-sdk/provider-catalog";
import { defineSingleProviderPluginEntry } from "openclaw/plugin-sdk/provider-entry";
import { applyVercelAiGatewayConfig, VERCEL_AI_GATEWAY_DEFAULT_MODEL_REF } from "./onboard.js";
import { buildVercelAiGatewayProvider } from "./provider-catalog.js";
const PROVIDER_ID = "vercel-ai-gateway";
export default definePluginEntry({
export default defineSingleProviderPluginEntry({
id: PROVIDER_ID,
name: "Vercel AI Gateway Provider",
description: "Bundled Vercel AI Gateway provider plugin",
register(api) {
api.registerProvider({
id: PROVIDER_ID,
label: "Vercel AI Gateway",
docsPath: "/providers/vercel-ai-gateway",
envVars: ["AI_GATEWAY_API_KEY"],
auth: [
createProviderApiKeyAuthMethod({
providerId: PROVIDER_ID,
methodId: "api-key",
label: "Vercel AI Gateway API key",
hint: "API key",
optionKey: "aiGatewayApiKey",
flagName: "--ai-gateway-api-key",
envVar: "AI_GATEWAY_API_KEY",
promptMessage: "Enter Vercel AI Gateway API key",
defaultModel: VERCEL_AI_GATEWAY_DEFAULT_MODEL_REF,
expectedProviders: ["vercel-ai-gateway"],
applyConfig: (cfg) => applyVercelAiGatewayConfig(cfg),
wizard: {
choiceId: "ai-gateway-api-key",
choiceLabel: "Vercel AI Gateway API key",
groupId: "ai-gateway",
groupLabel: "Vercel AI Gateway",
groupHint: "API key",
},
}),
],
catalog: {
order: "simple",
run: (ctx) =>
buildSingleProviderApiKeyCatalog({
ctx,
providerId: PROVIDER_ID,
buildProvider: buildVercelAiGatewayProvider,
}),
provider: {
label: "Vercel AI Gateway",
docsPath: "/providers/vercel-ai-gateway",
auth: [
{
methodId: "api-key",
label: "Vercel AI Gateway API key",
hint: "API key",
optionKey: "aiGatewayApiKey",
flagName: "--ai-gateway-api-key",
envVar: "AI_GATEWAY_API_KEY",
promptMessage: "Enter Vercel AI Gateway API key",
defaultModel: VERCEL_AI_GATEWAY_DEFAULT_MODEL_REF,
applyConfig: (cfg) => applyVercelAiGatewayConfig(cfg),
wizard: {
choiceId: "ai-gateway-api-key",
groupId: "ai-gateway",
},
},
});
],
catalog: {
buildProvider: buildVercelAiGatewayProvider,
},
},
});

View File

@ -1,6 +1,4 @@
import { definePluginEntry } from "openclaw/plugin-sdk/plugin-entry";
import { createProviderApiKeyAuthMethod } from "openclaw/plugin-sdk/provider-auth-api-key";
import { buildSingleProviderApiKeyCatalog } from "openclaw/plugin-sdk/provider-catalog";
import { defineSingleProviderPluginEntry } from "openclaw/plugin-sdk/provider-entry";
import { applyXaiModelCompat } from "openclaw/plugin-sdk/provider-models";
import { createToolStreamWrapper } from "openclaw/plugin-sdk/provider-stream";
import { applyXaiConfig, XAI_DEFAULT_MODEL_REF } from "./onboard.js";
@ -14,68 +12,54 @@ import { createXaiWebSearchProvider } from "./web-search.js";
const PROVIDER_ID = "xai";
export default definePluginEntry({
export default defineSingleProviderPluginEntry({
id: "xai",
name: "xAI Plugin",
description: "Bundled xAI plugin",
register(api) {
api.registerProvider({
id: PROVIDER_ID,
label: "xAI",
aliases: ["x-ai"],
docsPath: "/providers/xai",
envVars: ["XAI_API_KEY"],
auth: [
createProviderApiKeyAuthMethod({
providerId: PROVIDER_ID,
methodId: "api-key",
label: "xAI API key",
hint: "API key",
optionKey: "xaiApiKey",
flagName: "--xai-api-key",
envVar: "XAI_API_KEY",
promptMessage: "Enter xAI API key",
defaultModel: XAI_DEFAULT_MODEL_REF,
expectedProviders: ["xai"],
applyConfig: (cfg) => applyXaiConfig(cfg),
wizard: {
choiceId: "xai-api-key",
choiceLabel: "xAI API key",
groupId: "xai",
groupLabel: "xAI (Grok)",
groupHint: "API key",
},
}),
],
catalog: {
order: "simple",
run: (ctx) =>
buildSingleProviderApiKeyCatalog({
ctx,
providerId: PROVIDER_ID,
buildProvider: buildXaiProvider,
}),
provider: {
label: "xAI",
aliases: ["x-ai"],
docsPath: "/providers/xai",
auth: [
{
methodId: "api-key",
label: "xAI API key",
hint: "API key",
optionKey: "xaiApiKey",
flagName: "--xai-api-key",
envVar: "XAI_API_KEY",
promptMessage: "Enter xAI API key",
defaultModel: XAI_DEFAULT_MODEL_REF,
applyConfig: (cfg) => applyXaiConfig(cfg),
wizard: {
groupLabel: "xAI (Grok)",
},
},
prepareExtraParams: (ctx) => {
if (ctx.extraParams?.tool_stream !== undefined) {
return ctx.extraParams;
}
return {
...ctx.extraParams,
tool_stream: true,
};
},
wrapStreamFn: (ctx) =>
createToolStreamWrapper(
createXaiToolCallArgumentDecodingWrapper(
createXaiToolPayloadCompatibilityWrapper(ctx.streamFn),
),
ctx.extraParams?.tool_stream !== false,
],
catalog: {
buildProvider: buildXaiProvider,
},
prepareExtraParams: (ctx) => {
if (ctx.extraParams?.tool_stream !== undefined) {
return ctx.extraParams;
}
return {
...ctx.extraParams,
tool_stream: true,
};
},
wrapStreamFn: (ctx) =>
createToolStreamWrapper(
createXaiToolCallArgumentDecodingWrapper(
createXaiToolPayloadCompatibilityWrapper(ctx.streamFn),
),
normalizeResolvedModel: ({ model }) => applyXaiModelCompat(model),
resolveDynamicModel: (ctx) => resolveXaiForwardCompatModel({ providerId: PROVIDER_ID, ctx }),
isModernModelRef: ({ modelId }) => isModernXaiModel(modelId),
});
ctx.extraParams?.tool_stream !== false,
),
normalizeResolvedModel: ({ model }) => applyXaiModelCompat(model),
resolveDynamicModel: (ctx) => resolveXaiForwardCompatModel({ providerId: PROVIDER_ID, ctx }),
isModernModelRef: ({ modelId }) => isModernXaiModel(modelId),
},
register(api) {
api.registerWebSearchProvider(createXaiWebSearchProvider());
},
});

View File

@ -1,64 +1,43 @@
import { definePluginEntry } from "openclaw/plugin-sdk/plugin-entry";
import { createProviderApiKeyAuthMethod } from "openclaw/plugin-sdk/provider-auth";
import { buildSingleProviderApiKeyCatalog } from "openclaw/plugin-sdk/provider-catalog";
import { defineSingleProviderPluginEntry } from "openclaw/plugin-sdk/provider-entry";
import { PROVIDER_LABELS } from "openclaw/plugin-sdk/provider-usage";
import { applyXiaomiConfig, XIAOMI_DEFAULT_MODEL_REF } from "./onboard.js";
import { buildXiaomiProvider } from "./provider-catalog.js";
const PROVIDER_ID = "xiaomi";
export default definePluginEntry({
export default defineSingleProviderPluginEntry({
id: PROVIDER_ID,
name: "Xiaomi Provider",
description: "Bundled Xiaomi provider plugin",
register(api) {
api.registerProvider({
id: PROVIDER_ID,
label: "Xiaomi",
docsPath: "/providers/xiaomi",
envVars: ["XIAOMI_API_KEY"],
auth: [
createProviderApiKeyAuthMethod({
providerId: PROVIDER_ID,
methodId: "api-key",
label: "Xiaomi API key",
hint: "API key",
optionKey: "xiaomiApiKey",
flagName: "--xiaomi-api-key",
envVar: "XIAOMI_API_KEY",
promptMessage: "Enter Xiaomi API key",
defaultModel: XIAOMI_DEFAULT_MODEL_REF,
expectedProviders: ["xiaomi"],
applyConfig: (cfg) => applyXiaomiConfig(cfg),
wizard: {
choiceId: "xiaomi-api-key",
choiceLabel: "Xiaomi API key",
groupId: "xiaomi",
groupLabel: "Xiaomi",
groupHint: "API key",
},
}),
],
catalog: {
order: "simple",
run: (ctx) =>
buildSingleProviderApiKeyCatalog({
ctx,
providerId: PROVIDER_ID,
buildProvider: buildXiaomiProvider,
}),
provider: {
label: "Xiaomi",
docsPath: "/providers/xiaomi",
auth: [
{
methodId: "api-key",
label: "Xiaomi API key",
hint: "API key",
optionKey: "xiaomiApiKey",
flagName: "--xiaomi-api-key",
envVar: "XIAOMI_API_KEY",
promptMessage: "Enter Xiaomi API key",
defaultModel: XIAOMI_DEFAULT_MODEL_REF,
applyConfig: (cfg) => applyXiaomiConfig(cfg),
},
resolveUsageAuth: async (ctx) => {
const apiKey = ctx.resolveApiKeyFromConfigAndStore({
envDirect: [ctx.env.XIAOMI_API_KEY],
});
return apiKey ? { token: apiKey } : null;
},
fetchUsageSnapshot: async () => ({
provider: "xiaomi",
displayName: PROVIDER_LABELS.xiaomi,
windows: [],
}),
});
],
catalog: {
buildProvider: buildXiaomiProvider,
},
resolveUsageAuth: async (ctx) => {
const apiKey = ctx.resolveApiKeyFromConfigAndStore({
envDirect: [ctx.env.XIAOMI_API_KEY],
});
return apiKey ? { token: apiKey } : null;
},
fetchUsageSnapshot: async () => ({
provider: "xiaomi",
displayName: PROVIDER_LABELS.xiaomi,
windows: [],
}),
},
});

View File

@ -0,0 +1,186 @@
import { describe, expect, it } from "vitest";
import { capturePluginRegistration } from "../plugins/captured-registration.js";
import type { ProviderCatalogContext } from "../plugins/types.js";
import { defineSingleProviderPluginEntry } from "./provider-entry.js";
function createCatalogContext(
config: ProviderCatalogContext["config"] = {},
): ProviderCatalogContext {
return {
config,
env: {},
resolveProviderApiKey: () => ({ apiKey: "test-key" }),
resolveProviderAuth: () => ({
apiKey: "test-key",
mode: "api_key",
source: "env",
}),
};
}
describe("defineSingleProviderPluginEntry", () => {
it("registers a single provider with default wizard metadata", async () => {
const entry = defineSingleProviderPluginEntry({
id: "demo",
name: "Demo Provider",
description: "Demo provider plugin",
provider: {
label: "Demo",
docsPath: "/providers/demo",
auth: [
{
methodId: "api-key",
label: "Demo API key",
hint: "Shared key",
optionKey: "demoApiKey",
flagName: "--demo-api-key",
envVar: "DEMO_API_KEY",
promptMessage: "Enter Demo API key",
defaultModel: "demo/default",
},
],
catalog: {
buildProvider: () => ({
api: "openai-completions",
baseUrl: "https://api.demo.test/v1",
models: [{ id: "default", name: "Default" }],
}),
},
},
});
const captured = capturePluginRegistration(entry);
expect(captured.providers).toHaveLength(1);
const provider = captured.providers[0];
expect(provider).toMatchObject({
id: "demo",
label: "Demo",
docsPath: "/providers/demo",
envVars: ["DEMO_API_KEY"],
});
expect(provider?.auth).toHaveLength(1);
expect(provider?.auth[0]).toMatchObject({
id: "api-key",
label: "Demo API key",
hint: "Shared key",
});
expect(provider?.auth[0]?.wizard).toMatchObject({
choiceId: "demo-api-key",
choiceLabel: "Demo API key",
groupId: "demo",
groupLabel: "Demo",
groupHint: "Shared key",
methodId: "api-key",
});
const catalog = await provider?.catalog?.run(createCatalogContext());
expect(catalog).toEqual({
provider: {
api: "openai-completions",
apiKey: "test-key",
baseUrl: "https://api.demo.test/v1",
models: [{ id: "default", name: "Default" }],
},
});
});
it("supports provider overrides, explicit env vars, and extra registration", async () => {
const entry = defineSingleProviderPluginEntry({
id: "gateway-plugin",
name: "Gateway Provider",
description: "Gateway provider plugin",
provider: {
id: "gateway",
label: "Gateway",
aliases: ["gw"],
docsPath: "/providers/gateway",
envVars: ["GATEWAY_KEY", "SECONDARY_KEY"],
auth: [
{
methodId: "api-key",
label: "Gateway key",
hint: "Primary key",
optionKey: "gatewayKey",
flagName: "--gateway-key",
envVar: "GATEWAY_KEY",
promptMessage: "Enter Gateway key",
wizard: {
groupId: "shared-gateway",
groupLabel: "Shared Gateway",
},
},
],
catalog: {
buildProvider: () => ({
api: "openai-completions",
baseUrl: "https://gateway.test/v1",
models: [{ id: "router", name: "Router" }],
}),
allowExplicitBaseUrl: true,
},
capabilities: {
transcriptToolCallIdMode: "strict9",
},
},
register(api) {
api.registerWebSearchProvider({
id: "gateway-search",
label: "Gateway Search",
hint: "search",
envVars: [],
placeholder: "",
signupUrl: "https://example.com",
credentialPath: "tools.web.search.gateway.apiKey",
getCredentialValue: () => undefined,
setCredentialValue() {},
createTool: () => ({
description: "search",
parameters: {},
execute: async () => ({}),
}),
});
},
});
const captured = capturePluginRegistration(entry);
expect(captured.providers).toHaveLength(1);
expect(captured.webSearchProviders).toHaveLength(1);
const provider = captured.providers[0];
expect(provider).toMatchObject({
id: "gateway",
label: "Gateway",
aliases: ["gw"],
envVars: ["GATEWAY_KEY", "SECONDARY_KEY"],
capabilities: {
transcriptToolCallIdMode: "strict9",
},
});
expect(provider?.auth[0]?.wizard).toMatchObject({
choiceId: "gateway-api-key",
groupId: "shared-gateway",
groupLabel: "Shared Gateway",
groupHint: "Primary key",
});
const catalog = await provider?.catalog?.run(
createCatalogContext({
models: {
providers: {
gateway: {
baseUrl: "https://override.test/v1",
},
},
},
}),
);
expect(catalog).toEqual({
provider: {
api: "openai-completions",
apiKey: "test-key",
baseUrl: "https://override.test/v1",
models: [{ id: "router", name: "Router" }],
},
});
});
});

View File

@ -0,0 +1,142 @@
import type { ProviderPlugin, ProviderPluginWizardSetup } from "../plugins/types.js";
import { definePluginEntry } from "./plugin-entry.js";
import type {
OpenClawPluginApi,
OpenClawPluginConfigSchema,
OpenClawPluginDefinition,
} from "./plugin-entry.js";
import { createProviderApiKeyAuthMethod } from "./provider-auth.js";
import { buildSingleProviderApiKeyCatalog } from "./provider-catalog.js";
type ApiKeyAuthMethodOptions = Parameters<typeof createProviderApiKeyAuthMethod>[0];
export type SingleProviderPluginApiKeyAuthOptions = Omit<
ApiKeyAuthMethodOptions,
"providerId" | "expectedProviders" | "wizard"
> & {
expectedProviders?: string[];
wizard?: false | ProviderPluginWizardSetup;
};
export type SingleProviderPluginCatalogOptions = {
buildProvider: Parameters<typeof buildSingleProviderApiKeyCatalog>[0]["buildProvider"];
allowExplicitBaseUrl?: boolean;
};
export type SingleProviderPluginOptions = {
id: string;
name: string;
description: string;
kind?: OpenClawPluginDefinition["kind"];
configSchema?: OpenClawPluginConfigSchema | (() => OpenClawPluginConfigSchema);
provider?: {
id?: string;
label: string;
docsPath: string;
aliases?: string[];
envVars?: string[];
auth?: SingleProviderPluginApiKeyAuthOptions[];
catalog: SingleProviderPluginCatalogOptions;
} & Omit<
ProviderPlugin,
"id" | "label" | "docsPath" | "aliases" | "envVars" | "auth" | "catalog"
>;
register?: (api: OpenClawPluginApi) => void;
};
function resolveWizardSetup(params: {
providerId: string;
providerLabel: string;
auth: SingleProviderPluginApiKeyAuthOptions;
}): ProviderPluginWizardSetup | undefined {
if (params.auth.wizard === false) {
return undefined;
}
const wizard = params.auth.wizard ?? {};
const methodId = params.auth.methodId.trim();
return {
choiceId: wizard.choiceId ?? `${params.providerId}-${methodId}`,
choiceLabel: wizard.choiceLabel ?? params.auth.label,
...(wizard.choiceHint ? { choiceHint: wizard.choiceHint } : {}),
groupId: wizard.groupId ?? params.providerId,
groupLabel: wizard.groupLabel ?? params.providerLabel,
...((wizard.groupHint ?? params.auth.hint)
? { groupHint: wizard.groupHint ?? params.auth.hint }
: {}),
methodId,
...(wizard.onboardingScopes ? { onboardingScopes: wizard.onboardingScopes } : {}),
...(wizard.modelAllowlist ? { modelAllowlist: wizard.modelAllowlist } : {}),
};
}
function resolveEnvVars(params: {
envVars?: string[];
auth?: SingleProviderPluginApiKeyAuthOptions[];
}): string[] | undefined {
const combined = [
...(params.envVars ?? []),
...(params.auth ?? []).map((entry) => entry.envVar).filter(Boolean),
]
.map((value) => value.trim())
.filter(Boolean);
return combined.length > 0 ? [...new Set(combined)] : undefined;
}
export function defineSingleProviderPluginEntry(options: SingleProviderPluginOptions) {
return definePluginEntry({
id: options.id,
name: options.name,
description: options.description,
...(options.kind ? { kind: options.kind } : {}),
...(options.configSchema ? { configSchema: options.configSchema } : {}),
register(api) {
const provider = options.provider;
if (provider) {
const providerId = provider.id ?? options.id;
const envVars = resolveEnvVars({
envVars: provider.envVars,
auth: provider.auth,
});
const auth = (provider.auth ?? []).map((entry) => {
const { wizard: _wizard, ...authParams } = entry;
const wizard = resolveWizardSetup({
providerId,
providerLabel: provider.label,
auth: entry,
});
return createProviderApiKeyAuthMethod({
...authParams,
providerId,
expectedProviders: entry.expectedProviders ?? [providerId],
...(wizard ? { wizard } : {}),
});
});
api.registerProvider({
id: providerId,
label: provider.label,
docsPath: provider.docsPath,
...(provider.aliases ? { aliases: provider.aliases } : {}),
...(envVars ? { envVars } : {}),
auth,
catalog: {
order: "simple",
run: (ctx) =>
buildSingleProviderApiKeyCatalog({
ctx,
providerId,
buildProvider: provider.catalog.buildProvider,
...(provider.catalog.allowExplicitBaseUrl ? { allowExplicitBaseUrl: true } : {}),
}),
},
...Object.fromEntries(
Object.entries(provider).filter(
([key]) =>
!["id", "label", "docsPath", "aliases", "envVars", "auth", "catalog"].includes(key),
),
),
});
}
options.register?.(api);
},
});
}