refactor: validate provider plugin metadata
This commit is contained in:
@@ -771,18 +771,22 @@ are not just "OAuth helpers" anymore.
|
|||||||
|
|
||||||
### Provider plugin lifecycle
|
### Provider plugin lifecycle
|
||||||
|
|
||||||
A provider plugin can participate in four distinct phases:
|
A provider plugin can participate in five distinct phases:
|
||||||
|
|
||||||
1. **Auth**
|
1. **Auth**
|
||||||
`auth[].run(ctx)` performs OAuth, API-key capture, device code, or custom
|
`auth[].run(ctx)` performs OAuth, API-key capture, device code, or custom
|
||||||
setup and returns auth profiles plus optional config patches.
|
setup and returns auth profiles plus optional config patches.
|
||||||
2. **Wizard integration**
|
2. **Non-interactive setup**
|
||||||
|
`auth[].runNonInteractive(ctx)` handles `openclaw onboard --non-interactive`
|
||||||
|
without prompts. Use this when the provider needs custom headless setup
|
||||||
|
beyond the built-in simple API-key paths.
|
||||||
|
3. **Wizard integration**
|
||||||
`wizard.onboarding` adds an entry to `openclaw onboard`.
|
`wizard.onboarding` adds an entry to `openclaw onboard`.
|
||||||
`wizard.modelPicker` adds a setup entry to the model picker.
|
`wizard.modelPicker` adds a setup entry to the model picker.
|
||||||
3. **Implicit discovery**
|
4. **Implicit discovery**
|
||||||
`discovery.run(ctx)` can contribute provider config automatically during
|
`discovery.run(ctx)` can contribute provider config automatically during
|
||||||
model resolution/listing.
|
model resolution/listing.
|
||||||
4. **Post-selection follow-up**
|
5. **Post-selection follow-up**
|
||||||
`onModelSelected(ctx)` runs after a model is chosen. Use this for provider-
|
`onModelSelected(ctx)` runs after a model is chosen. Use this for provider-
|
||||||
specific work such as downloading a local model.
|
specific work such as downloading a local model.
|
||||||
|
|
||||||
@@ -790,6 +794,7 @@ This is the recommended split because these phases have different lifecycle
|
|||||||
requirements:
|
requirements:
|
||||||
|
|
||||||
- auth is interactive and writes credentials/config
|
- auth is interactive and writes credentials/config
|
||||||
|
- non-interactive setup is flag/env-driven and must not prompt
|
||||||
- wizard metadata is static and UI-facing
|
- wizard metadata is static and UI-facing
|
||||||
- discovery should be safe, quick, and failure-tolerant
|
- discovery should be safe, quick, and failure-tolerant
|
||||||
- post-select hooks are side effects tied to the chosen model
|
- post-select hooks are side effects tied to the chosen model
|
||||||
@@ -814,6 +819,32 @@ Core then:
|
|||||||
That means a provider plugin owns the provider-specific setup logic, while core
|
That means a provider plugin owns the provider-specific setup logic, while core
|
||||||
owns the generic persistence and config-merge path.
|
owns the generic persistence and config-merge path.
|
||||||
|
|
||||||
|
### Provider non-interactive contract
|
||||||
|
|
||||||
|
`auth[].runNonInteractive(ctx)` is optional. Implement it when the provider
|
||||||
|
needs headless setup that cannot be expressed through the built-in generic
|
||||||
|
API-key flows.
|
||||||
|
|
||||||
|
The non-interactive context includes:
|
||||||
|
|
||||||
|
- the current and base config
|
||||||
|
- parsed onboarding CLI options
|
||||||
|
- runtime logging/error helpers
|
||||||
|
- agent/workspace dirs
|
||||||
|
- `resolveApiKey(...)` to read provider keys from flags, env, or existing auth
|
||||||
|
profiles while honoring `--secret-input-mode`
|
||||||
|
- `toApiKeyCredential(...)` to convert a resolved key into an auth-profile
|
||||||
|
credential with the right plaintext vs secret-ref storage
|
||||||
|
|
||||||
|
Use this surface for providers such as:
|
||||||
|
|
||||||
|
- self-hosted OpenAI-compatible runtimes that need `--custom-base-url` +
|
||||||
|
`--custom-model-id`
|
||||||
|
- provider-specific non-interactive verification or config synthesis
|
||||||
|
|
||||||
|
Do not prompt from `runNonInteractive`. Reject missing inputs with actionable
|
||||||
|
errors instead.
|
||||||
|
|
||||||
### Provider wizard metadata
|
### Provider wizard metadata
|
||||||
|
|
||||||
`wizard.onboarding` controls how the provider appears in grouped onboarding:
|
`wizard.onboarding` controls how the provider appears in grouped onboarding:
|
||||||
@@ -836,6 +867,13 @@ entry in model selection:
|
|||||||
When a provider has multiple auth methods, the wizard can either point at one
|
When a provider has multiple auth methods, the wizard can either point at one
|
||||||
explicit method or let OpenClaw synthesize per-method choices.
|
explicit method or let OpenClaw synthesize per-method choices.
|
||||||
|
|
||||||
|
OpenClaw validates provider wizard metadata when the plugin registers:
|
||||||
|
|
||||||
|
- duplicate or blank auth-method ids are rejected
|
||||||
|
- wizard metadata is ignored when the provider has no auth methods
|
||||||
|
- invalid `methodId` bindings are downgraded to warnings and fall back to the
|
||||||
|
provider's remaining auth methods
|
||||||
|
|
||||||
### Provider discovery contract
|
### Provider discovery contract
|
||||||
|
|
||||||
`discovery.run(ctx)` returns one of:
|
`discovery.run(ctx)` returns one of:
|
||||||
@@ -970,6 +1008,9 @@ Notes:
|
|||||||
|
|
||||||
- `run` receives a `ProviderAuthContext` with `prompter`, `runtime`,
|
- `run` receives a `ProviderAuthContext` with `prompter`, `runtime`,
|
||||||
`openUrl`, and `oauth.createVpsAwareHandlers` helpers.
|
`openUrl`, and `oauth.createVpsAwareHandlers` helpers.
|
||||||
|
- `runNonInteractive` receives a `ProviderAuthMethodNonInteractiveContext`
|
||||||
|
with `opts`, `resolveApiKey`, and `toApiKeyCredential` helpers for
|
||||||
|
headless onboarding.
|
||||||
- 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.onboarding` adds a provider choice to `openclaw onboard`.
|
||||||
|
|||||||
127
src/plugins/provider-validation.test.ts
Normal file
127
src/plugins/provider-validation.test.ts
Normal file
@@ -0,0 +1,127 @@
|
|||||||
|
import { describe, expect, it } from "vitest";
|
||||||
|
import { normalizeRegisteredProvider } from "./provider-validation.js";
|
||||||
|
import type { PluginDiagnostic, ProviderPlugin } from "./types.js";
|
||||||
|
|
||||||
|
function collectDiagnostics() {
|
||||||
|
const diagnostics: PluginDiagnostic[] = [];
|
||||||
|
return {
|
||||||
|
diagnostics,
|
||||||
|
pushDiagnostic: (diag: PluginDiagnostic) => {
|
||||||
|
diagnostics.push(diag);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function makeProvider(overrides: Partial<ProviderPlugin>): ProviderPlugin {
|
||||||
|
return {
|
||||||
|
id: "demo",
|
||||||
|
label: "Demo",
|
||||||
|
auth: [],
|
||||||
|
...overrides,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
describe("normalizeRegisteredProvider", () => {
|
||||||
|
it("drops invalid and duplicate auth methods, and clears bad wizard method bindings", () => {
|
||||||
|
const { diagnostics, pushDiagnostic } = collectDiagnostics();
|
||||||
|
|
||||||
|
const provider = normalizeRegisteredProvider({
|
||||||
|
pluginId: "demo-plugin",
|
||||||
|
source: "/tmp/demo/index.ts",
|
||||||
|
provider: makeProvider({
|
||||||
|
id: " demo ",
|
||||||
|
label: " Demo Provider ",
|
||||||
|
aliases: [" alias-one ", "alias-one", ""],
|
||||||
|
envVars: [" DEMO_API_KEY ", "DEMO_API_KEY"],
|
||||||
|
auth: [
|
||||||
|
{
|
||||||
|
id: " primary ",
|
||||||
|
label: " Primary ",
|
||||||
|
kind: "custom",
|
||||||
|
run: async () => ({ profiles: [] }),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "primary",
|
||||||
|
label: "Duplicate",
|
||||||
|
kind: "custom",
|
||||||
|
run: async () => ({ profiles: [] }),
|
||||||
|
},
|
||||||
|
{ id: " ", label: "Missing", kind: "custom", run: async () => ({ profiles: [] }) },
|
||||||
|
],
|
||||||
|
wizard: {
|
||||||
|
onboarding: {
|
||||||
|
choiceId: " demo-choice ",
|
||||||
|
methodId: " missing ",
|
||||||
|
},
|
||||||
|
modelPicker: {
|
||||||
|
label: " Demo models ",
|
||||||
|
methodId: " missing ",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
pushDiagnostic,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(provider).toMatchObject({
|
||||||
|
id: "demo",
|
||||||
|
label: "Demo Provider",
|
||||||
|
aliases: ["alias-one"],
|
||||||
|
envVars: ["DEMO_API_KEY"],
|
||||||
|
auth: [{ id: "primary", label: "Primary" }],
|
||||||
|
wizard: {
|
||||||
|
onboarding: {
|
||||||
|
choiceId: "demo-choice",
|
||||||
|
},
|
||||||
|
modelPicker: {
|
||||||
|
label: "Demo models",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
expect(diagnostics.map((diag) => ({ level: diag.level, message: diag.message }))).toEqual([
|
||||||
|
{
|
||||||
|
level: "error",
|
||||||
|
message: 'provider "demo" auth method duplicated id "primary"',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
level: "error",
|
||||||
|
message: 'provider "demo" auth method missing id',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
level: "warn",
|
||||||
|
message:
|
||||||
|
'provider "demo" onboarding method "missing" not found; falling back to available methods',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
level: "warn",
|
||||||
|
message:
|
||||||
|
'provider "demo" model-picker method "missing" not found; falling back to available methods',
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("drops wizard metadata when a provider has no auth methods", () => {
|
||||||
|
const { diagnostics, pushDiagnostic } = collectDiagnostics();
|
||||||
|
|
||||||
|
const provider = normalizeRegisteredProvider({
|
||||||
|
pluginId: "demo-plugin",
|
||||||
|
source: "/tmp/demo/index.ts",
|
||||||
|
provider: makeProvider({
|
||||||
|
wizard: {
|
||||||
|
onboarding: {
|
||||||
|
choiceId: "demo",
|
||||||
|
},
|
||||||
|
modelPicker: {
|
||||||
|
label: "Demo",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
pushDiagnostic,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(provider?.wizard).toBeUndefined();
|
||||||
|
expect(diagnostics.map((diag) => diag.message)).toEqual([
|
||||||
|
'provider "demo" onboarding metadata ignored because it has no auth methods',
|
||||||
|
'provider "demo" model-picker metadata ignored because it has no auth methods',
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
});
|
||||||
232
src/plugins/provider-validation.ts
Normal file
232
src/plugins/provider-validation.ts
Normal file
@@ -0,0 +1,232 @@
|
|||||||
|
import type { PluginDiagnostic, ProviderAuthMethod, ProviderPlugin } from "./types.js";
|
||||||
|
|
||||||
|
function pushProviderDiagnostic(params: {
|
||||||
|
level: PluginDiagnostic["level"];
|
||||||
|
pluginId: string;
|
||||||
|
source: string;
|
||||||
|
message: string;
|
||||||
|
pushDiagnostic: (diag: PluginDiagnostic) => void;
|
||||||
|
}) {
|
||||||
|
params.pushDiagnostic({
|
||||||
|
level: params.level,
|
||||||
|
pluginId: params.pluginId,
|
||||||
|
source: params.source,
|
||||||
|
message: params.message,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizeText(value: string | undefined): string | undefined {
|
||||||
|
const trimmed = value?.trim();
|
||||||
|
return trimmed ? trimmed : undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizeTextList(values: string[] | undefined): string[] | undefined {
|
||||||
|
const normalized = Array.from(
|
||||||
|
new Set((values ?? []).map((value) => value.trim()).filter(Boolean)),
|
||||||
|
);
|
||||||
|
return normalized.length > 0 ? normalized : undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizeProviderAuthMethods(params: {
|
||||||
|
providerId: string;
|
||||||
|
pluginId: string;
|
||||||
|
source: string;
|
||||||
|
auth: ProviderAuthMethod[];
|
||||||
|
pushDiagnostic: (diag: PluginDiagnostic) => void;
|
||||||
|
}): ProviderAuthMethod[] {
|
||||||
|
const seenMethodIds = new Set<string>();
|
||||||
|
const normalized: ProviderAuthMethod[] = [];
|
||||||
|
|
||||||
|
for (const method of params.auth) {
|
||||||
|
const methodId = normalizeText(method.id);
|
||||||
|
if (!methodId) {
|
||||||
|
pushProviderDiagnostic({
|
||||||
|
level: "error",
|
||||||
|
pluginId: params.pluginId,
|
||||||
|
source: params.source,
|
||||||
|
message: `provider "${params.providerId}" auth method missing id`,
|
||||||
|
pushDiagnostic: params.pushDiagnostic,
|
||||||
|
});
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (seenMethodIds.has(methodId)) {
|
||||||
|
pushProviderDiagnostic({
|
||||||
|
level: "error",
|
||||||
|
pluginId: params.pluginId,
|
||||||
|
source: params.source,
|
||||||
|
message: `provider "${params.providerId}" auth method duplicated id "${methodId}"`,
|
||||||
|
pushDiagnostic: params.pushDiagnostic,
|
||||||
|
});
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
seenMethodIds.add(methodId);
|
||||||
|
normalized.push({
|
||||||
|
...method,
|
||||||
|
id: methodId,
|
||||||
|
label: normalizeText(method.label) ?? methodId,
|
||||||
|
...(normalizeText(method.hint) ? { hint: normalizeText(method.hint) } : {}),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return normalized;
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizeProviderWizard(params: {
|
||||||
|
providerId: string;
|
||||||
|
pluginId: string;
|
||||||
|
source: string;
|
||||||
|
auth: ProviderAuthMethod[];
|
||||||
|
wizard: ProviderPlugin["wizard"];
|
||||||
|
pushDiagnostic: (diag: PluginDiagnostic) => void;
|
||||||
|
}): ProviderPlugin["wizard"] {
|
||||||
|
if (!params.wizard) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
const hasAuthMethods = params.auth.length > 0;
|
||||||
|
const hasMethod = (methodId: string | undefined) =>
|
||||||
|
Boolean(methodId && params.auth.some((method) => method.id === methodId));
|
||||||
|
|
||||||
|
const normalizeOnboarding = () => {
|
||||||
|
const onboarding = params.wizard?.onboarding;
|
||||||
|
if (!onboarding) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
if (!hasAuthMethods) {
|
||||||
|
pushProviderDiagnostic({
|
||||||
|
level: "warn",
|
||||||
|
pluginId: params.pluginId,
|
||||||
|
source: params.source,
|
||||||
|
message: `provider "${params.providerId}" onboarding metadata ignored because it has no auth methods`,
|
||||||
|
pushDiagnostic: params.pushDiagnostic,
|
||||||
|
});
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
const methodId = normalizeText(onboarding.methodId);
|
||||||
|
if (methodId && !hasMethod(methodId)) {
|
||||||
|
pushProviderDiagnostic({
|
||||||
|
level: "warn",
|
||||||
|
pluginId: params.pluginId,
|
||||||
|
source: params.source,
|
||||||
|
message: `provider "${params.providerId}" onboarding method "${methodId}" not found; falling back to available methods`,
|
||||||
|
pushDiagnostic: params.pushDiagnostic,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
...(normalizeText(onboarding.choiceId)
|
||||||
|
? { choiceId: normalizeText(onboarding.choiceId) }
|
||||||
|
: {}),
|
||||||
|
...(normalizeText(onboarding.choiceLabel)
|
||||||
|
? { choiceLabel: normalizeText(onboarding.choiceLabel) }
|
||||||
|
: {}),
|
||||||
|
...(normalizeText(onboarding.choiceHint)
|
||||||
|
? { choiceHint: normalizeText(onboarding.choiceHint) }
|
||||||
|
: {}),
|
||||||
|
...(normalizeText(onboarding.groupId) ? { groupId: normalizeText(onboarding.groupId) } : {}),
|
||||||
|
...(normalizeText(onboarding.groupLabel)
|
||||||
|
? { groupLabel: normalizeText(onboarding.groupLabel) }
|
||||||
|
: {}),
|
||||||
|
...(normalizeText(onboarding.groupHint)
|
||||||
|
? { groupHint: normalizeText(onboarding.groupHint) }
|
||||||
|
: {}),
|
||||||
|
...(methodId && hasMethod(methodId) ? { methodId } : {}),
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const normalizeModelPicker = () => {
|
||||||
|
const modelPicker = params.wizard?.modelPicker;
|
||||||
|
if (!modelPicker) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
if (!hasAuthMethods) {
|
||||||
|
pushProviderDiagnostic({
|
||||||
|
level: "warn",
|
||||||
|
pluginId: params.pluginId,
|
||||||
|
source: params.source,
|
||||||
|
message: `provider "${params.providerId}" model-picker metadata ignored because it has no auth methods`,
|
||||||
|
pushDiagnostic: params.pushDiagnostic,
|
||||||
|
});
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
const methodId = normalizeText(modelPicker.methodId);
|
||||||
|
if (methodId && !hasMethod(methodId)) {
|
||||||
|
pushProviderDiagnostic({
|
||||||
|
level: "warn",
|
||||||
|
pluginId: params.pluginId,
|
||||||
|
source: params.source,
|
||||||
|
message: `provider "${params.providerId}" model-picker method "${methodId}" not found; falling back to available methods`,
|
||||||
|
pushDiagnostic: params.pushDiagnostic,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
...(normalizeText(modelPicker.label) ? { label: normalizeText(modelPicker.label) } : {}),
|
||||||
|
...(normalizeText(modelPicker.hint) ? { hint: normalizeText(modelPicker.hint) } : {}),
|
||||||
|
...(methodId && hasMethod(methodId) ? { methodId } : {}),
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const onboarding = normalizeOnboarding();
|
||||||
|
const modelPicker = normalizeModelPicker();
|
||||||
|
if (!onboarding && !modelPicker) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
...(onboarding ? { onboarding } : {}),
|
||||||
|
...(modelPicker ? { modelPicker } : {}),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function normalizeRegisteredProvider(params: {
|
||||||
|
pluginId: string;
|
||||||
|
source: string;
|
||||||
|
provider: ProviderPlugin;
|
||||||
|
pushDiagnostic: (diag: PluginDiagnostic) => void;
|
||||||
|
}): ProviderPlugin | null {
|
||||||
|
const id = normalizeText(params.provider.id);
|
||||||
|
if (!id) {
|
||||||
|
pushProviderDiagnostic({
|
||||||
|
level: "error",
|
||||||
|
pluginId: params.pluginId,
|
||||||
|
source: params.source,
|
||||||
|
message: "provider registration missing id",
|
||||||
|
pushDiagnostic: params.pushDiagnostic,
|
||||||
|
});
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auth = normalizeProviderAuthMethods({
|
||||||
|
providerId: id,
|
||||||
|
pluginId: params.pluginId,
|
||||||
|
source: params.source,
|
||||||
|
auth: params.provider.auth ?? [],
|
||||||
|
pushDiagnostic: params.pushDiagnostic,
|
||||||
|
});
|
||||||
|
const docsPath = normalizeText(params.provider.docsPath);
|
||||||
|
const aliases = normalizeTextList(params.provider.aliases);
|
||||||
|
const envVars = normalizeTextList(params.provider.envVars);
|
||||||
|
const wizard = normalizeProviderWizard({
|
||||||
|
providerId: id,
|
||||||
|
pluginId: params.pluginId,
|
||||||
|
source: params.source,
|
||||||
|
auth,
|
||||||
|
wizard: params.provider.wizard,
|
||||||
|
pushDiagnostic: params.pushDiagnostic,
|
||||||
|
});
|
||||||
|
const {
|
||||||
|
wizard: _ignoredWizard,
|
||||||
|
docsPath: _ignoredDocsPath,
|
||||||
|
aliases: _ignoredAliases,
|
||||||
|
envVars: _ignoredEnvVars,
|
||||||
|
...restProvider
|
||||||
|
} = params.provider;
|
||||||
|
return {
|
||||||
|
...restProvider,
|
||||||
|
id,
|
||||||
|
label: normalizeText(params.provider.label) ?? id,
|
||||||
|
...(docsPath ? { docsPath } : {}),
|
||||||
|
...(aliases ? { aliases } : {}),
|
||||||
|
...(envVars ? { envVars } : {}),
|
||||||
|
auth,
|
||||||
|
...(wizard ? { wizard } : {}),
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -13,6 +13,7 @@ import { resolveUserPath } from "../utils.js";
|
|||||||
import { registerPluginCommand } from "./commands.js";
|
import { registerPluginCommand } from "./commands.js";
|
||||||
import { normalizePluginHttpPath } from "./http-path.js";
|
import { normalizePluginHttpPath } from "./http-path.js";
|
||||||
import { findOverlappingPluginHttpRoute } from "./http-route-overlap.js";
|
import { findOverlappingPluginHttpRoute } from "./http-route-overlap.js";
|
||||||
|
import { normalizeRegisteredProvider } from "./provider-validation.js";
|
||||||
import type { PluginRuntime } from "./runtime/types.js";
|
import type { PluginRuntime } from "./runtime/types.js";
|
||||||
import {
|
import {
|
||||||
isPluginHookName,
|
isPluginHookName,
|
||||||
@@ -428,16 +429,16 @@ export function createPluginRegistry(registryParams: PluginRegistryParams) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const registerProvider = (record: PluginRecord, provider: ProviderPlugin) => {
|
const registerProvider = (record: PluginRecord, provider: ProviderPlugin) => {
|
||||||
const id = typeof provider?.id === "string" ? provider.id.trim() : "";
|
const normalizedProvider = normalizeRegisteredProvider({
|
||||||
if (!id) {
|
pluginId: record.id,
|
||||||
pushDiagnostic({
|
source: record.source,
|
||||||
level: "error",
|
provider,
|
||||||
pluginId: record.id,
|
pushDiagnostic,
|
||||||
source: record.source,
|
});
|
||||||
message: "provider registration missing id",
|
if (!normalizedProvider) {
|
||||||
});
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
const id = normalizedProvider.id;
|
||||||
const existing = registry.providers.find((entry) => entry.provider.id === id);
|
const existing = registry.providers.find((entry) => entry.provider.id === id);
|
||||||
if (existing) {
|
if (existing) {
|
||||||
pushDiagnostic({
|
pushDiagnostic({
|
||||||
@@ -451,7 +452,7 @@ export function createPluginRegistry(registryParams: PluginRegistryParams) {
|
|||||||
record.providerIds.push(id);
|
record.providerIds.push(id);
|
||||||
registry.providers.push({
|
registry.providers.push({
|
||||||
pluginId: record.id,
|
pluginId: record.id,
|
||||||
provider,
|
provider: normalizedProvider,
|
||||||
source: record.source,
|
source: record.source,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user