mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-29 20:38:40 +00:00
fix: keep provider catalog entries on release live path
This commit is contained in:
@@ -1943,6 +1943,65 @@ function createStaticLiveModelRegistry(models: Array<Model>): LiveModelRegistry
|
||||
};
|
||||
}
|
||||
|
||||
function toLiveModelConfig(model: Model): NonNullable<ModelProviderConfig["models"]>[number] {
|
||||
return {
|
||||
id: model.id,
|
||||
name: model.name,
|
||||
api: model.api as ModelProviderConfig["api"],
|
||||
baseUrl: model.baseUrl,
|
||||
input: model.input ?? ["text"],
|
||||
reasoning: model.reasoning,
|
||||
cost: model.cost,
|
||||
contextWindow: model.contextWindow,
|
||||
maxTokens: model.maxTokens,
|
||||
...(model.compat ? { compat: model.compat } : {}),
|
||||
};
|
||||
}
|
||||
|
||||
function mergeLiveProviderConfig(params: {
|
||||
base: ModelProviderConfig | undefined;
|
||||
discovered: ModelProviderConfig;
|
||||
}): ModelProviderConfig {
|
||||
const baseModels = params.base?.models ?? [];
|
||||
const discoveredModels = params.discovered.models ?? [];
|
||||
const mergedModels = new Map<string, NonNullable<ModelProviderConfig["models"]>[number]>();
|
||||
for (const model of discoveredModels) {
|
||||
if (model.id) {
|
||||
mergedModels.set(model.id, model);
|
||||
}
|
||||
}
|
||||
for (const model of baseModels) {
|
||||
if (model.id) {
|
||||
mergedModels.set(model.id, model);
|
||||
}
|
||||
}
|
||||
return {
|
||||
...params.discovered,
|
||||
...params.base,
|
||||
api: params.base?.api ?? params.discovered.api,
|
||||
baseUrl: params.base?.baseUrl ?? params.discovered.baseUrl,
|
||||
models: [...mergedModels.values()],
|
||||
};
|
||||
}
|
||||
|
||||
function buildLiveProviderConfigs(candidates: Array<Model>): Record<string, ModelProviderConfig> {
|
||||
const providers: Record<string, ModelProviderConfig> = {};
|
||||
for (const model of candidates) {
|
||||
const existing = providers[model.provider];
|
||||
if (existing) {
|
||||
existing.models ??= [];
|
||||
existing.models.push(toLiveModelConfig(model));
|
||||
continue;
|
||||
}
|
||||
providers[model.provider] = {
|
||||
api: model.api as ModelProviderConfig["api"],
|
||||
baseUrl: model.baseUrl,
|
||||
models: [toLiveModelConfig(model)],
|
||||
};
|
||||
}
|
||||
return providers;
|
||||
}
|
||||
|
||||
function parseExplicitLiveModelRef(
|
||||
raw: string,
|
||||
providerFilter: Set<string> | null,
|
||||
@@ -2045,8 +2104,16 @@ function buildLiveGatewayConfig(params: {
|
||||
const providerOverrides = params.providerOverrides ?? {};
|
||||
const lmstudioProvider = params.cfg.models?.providers?.lmstudio;
|
||||
const baseProviders = params.cfg.models?.providers ?? {};
|
||||
const candidateProviders = buildLiveProviderConfigs(params.candidates);
|
||||
const discoveredProviders = Object.fromEntries(
|
||||
Object.entries(candidateProviders).map(([provider, discovered]) => [
|
||||
provider,
|
||||
mergeLiveProviderConfig({ base: baseProviders[provider], discovered }),
|
||||
]),
|
||||
);
|
||||
const nextProviders = {
|
||||
...baseProviders,
|
||||
...discoveredProviders,
|
||||
...(lmstudioProvider
|
||||
? {
|
||||
lmstudio: {
|
||||
@@ -2954,15 +3021,15 @@ describeLive("gateway live (dev agent, profile keys)", () => {
|
||||
);
|
||||
const workspaceDir = resolveAgentWorkspaceDir(cfg, DEFAULT_AGENT_ID);
|
||||
logProgress("[all-models] preparing models.json");
|
||||
await withGatewayLiveSetupTimeout(
|
||||
const modelsJsonResult = await withGatewayLiveSetupTimeout(
|
||||
ensureOpenClawModelsJson(cfg, undefined, {
|
||||
workspaceDir,
|
||||
...(providerList ? { providerDiscoveryProviderIds: providerList } : {}),
|
||||
}),
|
||||
"[all-models] prepare models.json",
|
||||
);
|
||||
const agentDir = modelsJsonResult.agentDir;
|
||||
|
||||
const agentDir = resolveDefaultAgentDir(cfg);
|
||||
const rawModels = process.env.OPENCLAW_LIVE_GATEWAY_MODELS?.trim();
|
||||
const useModern = !rawModels || rawModels === "modern" || rawModels === "all";
|
||||
const useExplicit = Boolean(rawModels) && !useModern;
|
||||
|
||||
@@ -157,23 +157,20 @@ describe("resolvePluginDiscoveryProvidersRuntime", () => {
|
||||
);
|
||||
});
|
||||
|
||||
it("falls back to full provider plugins when discovery entries only expose static catalogs", () => {
|
||||
const fullProvider = createProvider({ id: "deepseek", mode: "catalog" });
|
||||
mocks.loadSource.mockReturnValue(createProvider({ id: "deepseek", mode: "static" }));
|
||||
mocks.resolvePluginProviders.mockReturnValue([fullProvider]);
|
||||
it("uses static provider catalog entries without loading the full plugin", () => {
|
||||
const staticProvider = createProvider({ id: "deepseek", mode: "static" });
|
||||
mocks.loadSource.mockReturnValue(staticProvider);
|
||||
|
||||
expect(resolvePluginDiscoveryProvidersRuntime({})).toEqual([fullProvider]);
|
||||
expect(mocks.resolvePluginProviders).toHaveBeenCalledTimes(1);
|
||||
const params = requireResolvePluginProvidersParams();
|
||||
expect(params.onlyPluginIds).toEqual(["deepseek"]);
|
||||
expect(resolvePluginDiscoveryProvidersRuntime({})).toEqual([
|
||||
{ ...staticProvider, pluginId: "deepseek" },
|
||||
]);
|
||||
expect(mocks.resolvePluginProviders).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("keeps unscoped discovery bounded for mixed live and static-only entries", () => {
|
||||
const codexEntryProvider = createProvider({ id: "codex", mode: "catalog" });
|
||||
const fullProviders = [
|
||||
createProvider({ id: "deepseek", mode: "catalog" }),
|
||||
createProvider({ id: "kilocode", mode: "catalog" }),
|
||||
];
|
||||
const deepseekEntryProvider = createProvider({ id: "deepseek", mode: "static" });
|
||||
const fullProviders = [createProvider({ id: "kilocode", mode: "catalog" })];
|
||||
mocks.resolveDiscoveredProviderPluginIds.mockReturnValue([
|
||||
"codex",
|
||||
"deepseek",
|
||||
@@ -199,9 +196,7 @@ describe("resolvePluginDiscoveryProvidersRuntime", () => {
|
||||
},
|
||||
});
|
||||
mocks.loadSource.mockImplementation((modulePath: string) =>
|
||||
modulePath.includes("/codex/")
|
||||
? codexEntryProvider
|
||||
: createProvider({ id: "deepseek", mode: "static" }),
|
||||
modulePath.includes("/codex/") ? codexEntryProvider : deepseekEntryProvider,
|
||||
);
|
||||
mocks.resolvePluginProviders.mockReturnValue(fullProviders);
|
||||
|
||||
@@ -209,10 +204,14 @@ describe("resolvePluginDiscoveryProvidersRuntime", () => {
|
||||
resolvePluginDiscoveryProvidersRuntime({
|
||||
env: { KILOCODE_API_KEY: "sk-test" } as NodeJS.ProcessEnv,
|
||||
}),
|
||||
).toEqual([{ ...codexEntryProvider, pluginId: "codex" }, ...fullProviders]);
|
||||
).toEqual([
|
||||
{ ...codexEntryProvider, pluginId: "codex" },
|
||||
{ ...deepseekEntryProvider, pluginId: "deepseek" },
|
||||
...fullProviders,
|
||||
]);
|
||||
expect(mocks.resolvePluginProviders).toHaveBeenCalledTimes(1);
|
||||
const params = requireResolvePluginProvidersParams();
|
||||
expect(params.onlyPluginIds).toEqual(["deepseek", "kilocode"]);
|
||||
expect(params.onlyPluginIds).toEqual(["kilocode"]);
|
||||
});
|
||||
|
||||
it("falls back to full provider plugins when setup provider env vars are configured", () => {
|
||||
|
||||
@@ -262,24 +262,13 @@ function resolveProviderDiscoveryEntryPlugins(params: {
|
||||
|
||||
function resolveSelectiveFullPluginIds(params: {
|
||||
entryResult: ProviderDiscoveryEntryResult;
|
||||
runtimeEntryProviders: ProviderPlugin[];
|
||||
env: NodeJS.ProcessEnv;
|
||||
}): string[] {
|
||||
const runtimeEntryProviderIds = new Set(
|
||||
params.runtimeEntryProviders
|
||||
.map((provider) => provider.pluginId)
|
||||
.filter((pluginId): pluginId is string => typeof pluginId === "string" && pluginId !== ""),
|
||||
);
|
||||
const staticOnlyEntryPluginIds = params.entryResult.providers
|
||||
.filter((provider) => !runtimeEntryProviderIds.has(provider.pluginId ?? ""))
|
||||
.filter((provider) => !hasLiveProviderDiscoveryHook(provider))
|
||||
.map((provider) => provider.pluginId)
|
||||
.filter((pluginId): pluginId is string => typeof pluginId === "string" && pluginId !== "");
|
||||
const missingEntryCredentialPluginIds = params.entryResult.pluginRecords
|
||||
.filter((plugin) => !params.entryResult.entryPluginIds.has(plugin.id))
|
||||
.filter((plugin) => hasProviderAuthEnvCredential(plugin, params.env))
|
||||
.map((plugin) => plugin.id);
|
||||
return sortUniqueStrings([...staticOnlyEntryPluginIds, ...missingEntryCredentialPluginIds]);
|
||||
return sortUniqueStrings(missingEntryCredentialPluginIds);
|
||||
}
|
||||
|
||||
function resolveMissingEntryPluginIds(entryResult: ProviderDiscoveryEntryResult): string[] {
|
||||
@@ -295,7 +284,7 @@ function resolveRuntimeEntryProviders(entryResult: ProviderDiscoveryEntryResult)
|
||||
}
|
||||
return Boolean(
|
||||
provider.pluginId &&
|
||||
entryResult.manifestEntryPluginIds.has(provider.pluginId) &&
|
||||
entryResult.entryPluginIds.has(provider.pluginId) &&
|
||||
typeof provider.staticCatalog?.run === "function",
|
||||
);
|
||||
});
|
||||
@@ -324,7 +313,6 @@ export function resolvePluginDiscoveryProvidersRuntime(params: {
|
||||
if (params.onlyPluginIds === undefined && runtimeEntryProviders.length > 0) {
|
||||
const fullPluginIds = resolveSelectiveFullPluginIds({
|
||||
entryResult,
|
||||
runtimeEntryProviders,
|
||||
env,
|
||||
});
|
||||
const fullProviders =
|
||||
|
||||
Reference in New Issue
Block a user