























@@ -1,5 +1,5 @@
11import type { OpenClawConfig } from "../config/types.openclaw.js";
2-import { loadPluginManifestRegistry } from "./manifest-registry.js";
2+import { loadPluginManifestRegistry, type PluginManifestRecord } from "./manifest-registry.js";
33import { resolveDiscoveredProviderPluginIds } from "./providers.js";
44import { resolvePluginProviders } from "./providers.runtime.js";
55import { createPluginSourceLoader } from "./source-loader.js";
@@ -17,6 +17,8 @@ type ProviderDiscoveryModule =
1717type ProviderDiscoveryEntryResult = {
1818providers: ProviderPlugin[];
1919complete: boolean;
20+pluginRecords: PluginManifestRecord[];
21+entryPluginIds: Set<string>;
2022};
21232224function normalizeDiscoveryModule(value: ProviderDiscoveryModule): ProviderPlugin[] {
@@ -48,6 +50,22 @@ function hasLiveProviderDiscoveryHook(provider: ProviderPlugin): boolean {
4850);
4951}
505253+function hasProviderAuthEnvCredential(
54+plugin: PluginManifestRecord,
55+env: NodeJS.ProcessEnv,
56+): boolean {
57+return Object.values(plugin.providerAuthEnvVars ?? {}).some((envVars) =>
58+envVars.some((name) => {
59+const value = env[name]?.trim();
60+return value !== undefined && value !== "";
61+}),
62+);
63+}
64+65+function dedupeSorted(values: Iterable<string>): string[] {
66+return [...new Set(values)].toSorted((left, right) => left.localeCompare(right));
67+}
68+5169function resolveProviderDiscoveryEntryPlugins(params: {
5270config?: OpenClawConfig;
5371workspaceDir?: string;
@@ -59,19 +77,21 @@ function resolveProviderDiscoveryEntryPlugins(params: {
5977}): ProviderDiscoveryEntryResult {
6078const pluginIds = resolveDiscoveredProviderPluginIds(params);
6179const pluginIdSet = new Set(pluginIds);
62-const records = loadPluginManifestRegistry(params).plugins.filter(
63-(plugin) => plugin.providerDiscoverySource && pluginIdSet.has(plugin.id),
80+const pluginRecords = loadPluginManifestRegistry(params).plugins.filter((plugin) =>
81+pluginIdSet.has(plugin.id),
6482);
65-if (records.length === 0) {
66-return { providers: [], complete: false };
83+const entryRecords = pluginRecords.filter((plugin) => plugin.providerDiscoverySource);
84+const entryPluginIds = new Set(entryRecords.map((plugin) => plugin.id));
85+if (entryRecords.length === 0) {
86+return { providers: [], complete: false, pluginRecords, entryPluginIds };
6787}
68-const complete = records.length === pluginIdSet.size;
88+const complete = entryRecords.length === pluginIdSet.size;
6989if (params.requireCompleteDiscoveryEntryCoverage && !complete) {
70-return { providers: [], complete: false };
90+return { providers: [], complete: false, pluginRecords, entryPluginIds };
7191}
7292const loadSource = createPluginSourceLoader();
7393const providers: ProviderPlugin[] = [];
74-for (const manifest of records) {
94+for (const manifest of entryRecords) {
7595try {
7696const moduleExport = loadSource(manifest.providerDiscoverySource!) as ProviderDiscoveryModule;
7797providers.push(
@@ -82,10 +102,26 @@ function resolveProviderDiscoveryEntryPlugins(params: {
82102} catch {
83103// Discovery fast path is optional. Fall back to the full plugin loader
84104// below so existing plugin diagnostics/load behavior remains canonical.
85-return { providers: [], complete: false };
105+return { providers: [], complete: false, pluginRecords, entryPluginIds };
86106}
87107}
88-return { providers, complete };
108+return { providers, complete, pluginRecords, entryPluginIds };
109+}
110+111+function resolveSelectiveFullPluginIds(params: {
112+entryResult: ProviderDiscoveryEntryResult;
113+entryProviders: ProviderPlugin[];
114+env: NodeJS.ProcessEnv;
115+}): string[] {
116+const staticOnlyEntryPluginIds = params.entryProviders
117+.filter((provider) => !hasLiveProviderDiscoveryHook(provider))
118+.map((provider) => provider.pluginId)
119+.filter((pluginId): pluginId is string => typeof pluginId === "string" && pluginId !== "");
120+const missingEntryCredentialPluginIds = params.entryResult.pluginRecords
121+.filter((plugin) => !params.entryResult.entryPluginIds.has(plugin.id))
122+.filter((plugin) => hasProviderAuthEnvCredential(plugin, params.env))
123+.map((plugin) => plugin.id);
124+return dedupeSorted([...staticOnlyEntryPluginIds, ...missingEntryCredentialPluginIds]);
89125}
9012691127export function resolvePluginDiscoveryProvidersRuntime(params: {
@@ -97,19 +133,35 @@ export function resolvePluginDiscoveryProvidersRuntime(params: {
97133requireCompleteDiscoveryEntryCoverage?: boolean;
98134discoveryEntriesOnly?: boolean;
99135}): ProviderPlugin[] {
136+const env = params.env ?? process.env;
100137const entryResult = resolveProviderDiscoveryEntryPlugins(params);
101138if (params.discoveryEntriesOnly === true) {
102139return entryResult.providers;
103140}
104-if (
105-entryResult.complete &&
106-entryResult.providers.length > 0 &&
107-entryResult.providers.every(hasLiveProviderDiscoveryHook)
108-) {
109-return entryResult.providers;
141+const liveEntryProviders = entryResult.providers.filter(hasLiveProviderDiscoveryHook);
142+if (entryResult.complete && liveEntryProviders.length === entryResult.providers.length) {
143+return liveEntryProviders;
144+}
145+if (params.onlyPluginIds === undefined && entryResult.providers.length > 0) {
146+const fullPluginIds = resolveSelectiveFullPluginIds({
147+ entryResult,
148+entryProviders: entryResult.providers,
149+ env,
150+});
151+const fullProviders =
152+fullPluginIds.length > 0
153+ ? resolvePluginProviders({
154+ ...params,
155+ env,
156+onlyPluginIds: fullPluginIds,
157+bundledProviderAllowlistCompat: true,
158+})
159+ : [];
160+return [...liveEntryProviders, ...fullProviders];
110161}
111162return resolvePluginProviders({
112163 ...params,
164+ env,
113165bundledProviderAllowlistCompat: true,
114166});
115167}
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。