mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 05:30:42 +00:00
fix: preserve provider filtered catalog correctness
This commit is contained in:
@@ -1,31 +1,17 @@
|
||||
import type { ProviderPlugin } from "openclaw/plugin-sdk/provider-model-shared";
|
||||
import { buildArceeOpenRouterProvider, buildArceeProvider } from "./provider-catalog.js";
|
||||
import { buildArceeProvider } from "./provider-catalog.js";
|
||||
|
||||
export const arceeProviderDiscovery: ProviderPlugin[] = [
|
||||
{
|
||||
id: "arcee",
|
||||
label: "Arcee AI",
|
||||
docsPath: "/providers/models",
|
||||
auth: [],
|
||||
staticCatalog: {
|
||||
order: "simple",
|
||||
run: async () => ({
|
||||
provider: buildArceeProvider(),
|
||||
}),
|
||||
},
|
||||
export const arceeProviderDiscovery: ProviderPlugin = {
|
||||
id: "arcee",
|
||||
label: "Arcee AI",
|
||||
docsPath: "/providers/models",
|
||||
auth: [],
|
||||
staticCatalog: {
|
||||
order: "simple",
|
||||
run: async () => ({
|
||||
provider: buildArceeProvider(),
|
||||
}),
|
||||
},
|
||||
{
|
||||
id: "arcee-openrouter",
|
||||
label: "Arcee AI via OpenRouter",
|
||||
docsPath: "/providers/models",
|
||||
auth: [],
|
||||
staticCatalog: {
|
||||
order: "simple",
|
||||
run: async () => ({
|
||||
provider: buildArceeOpenRouterProvider(),
|
||||
}),
|
||||
},
|
||||
},
|
||||
];
|
||||
};
|
||||
|
||||
export default arceeProviderDiscovery;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import {
|
||||
hasProviderStaticCatalogForFilter,
|
||||
loadProviderCatalogModelsForList,
|
||||
resolveProviderCatalogPluginIdsForFilter,
|
||||
} from "./list.provider-catalog.js";
|
||||
@@ -87,6 +88,18 @@ const openaiProvider = {
|
||||
},
|
||||
};
|
||||
|
||||
const catalogOnlyProvider = {
|
||||
id: "ollama",
|
||||
pluginId: "ollama",
|
||||
label: "Ollama",
|
||||
auth: [],
|
||||
catalog: {
|
||||
run: async () => ({
|
||||
provider: { baseUrl: "http://127.0.0.1:11434", models: [] },
|
||||
}),
|
||||
},
|
||||
};
|
||||
|
||||
const defaultProviders = [chutesProvider, moonshotProvider, openaiProvider];
|
||||
|
||||
describe("loadProviderCatalogModelsForList", () => {
|
||||
@@ -96,10 +109,13 @@ describe("loadProviderCatalogModelsForList", () => {
|
||||
"chutes",
|
||||
"moonshot",
|
||||
"openai",
|
||||
"ollama",
|
||||
]);
|
||||
providerDiscoveryMocks.resolveOwningPluginIdsForProvider.mockImplementation(
|
||||
({ provider }: { provider: string }) =>
|
||||
defaultProviders.some((entry) => entry.id === provider) ? [provider] : undefined,
|
||||
[...defaultProviders, catalogOnlyProvider].some((entry) => entry.id === provider)
|
||||
? [provider]
|
||||
: undefined,
|
||||
);
|
||||
providerDiscoveryMocks.resolveProviderContractPluginIdsForProviderAlias.mockImplementation(
|
||||
(provider: string) => (provider === "azure-openai-responses" ? ["openai"] : undefined),
|
||||
@@ -146,6 +162,27 @@ describe("loadProviderCatalogModelsForList", () => {
|
||||
expect.objectContaining({
|
||||
onlyPluginIds: ["moonshot"],
|
||||
requireCompleteDiscoveryEntryCoverage: true,
|
||||
discoveryEntriesOnly: true,
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it("only skips registry for providers with actual static catalogs", async () => {
|
||||
providerDiscoveryMocks.resolvePluginDiscoveryProviders.mockResolvedValue([catalogOnlyProvider]);
|
||||
|
||||
await expect(
|
||||
hasProviderStaticCatalogForFilter({
|
||||
cfg: baseParams.cfg,
|
||||
env: baseParams.env,
|
||||
providerFilter: "ollama",
|
||||
}),
|
||||
).resolves.toBe(false);
|
||||
|
||||
expect(providerDiscoveryMocks.resolvePluginDiscoveryProviders).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
onlyPluginIds: ["ollama"],
|
||||
requireCompleteDiscoveryEntryCoverage: true,
|
||||
discoveryEntriesOnly: true,
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
@@ -4,7 +4,6 @@ import type { ModelProviderConfig } from "../../config/types.models.js";
|
||||
import type { OpenClawConfig } from "../../config/types.openclaw.js";
|
||||
import { formatErrorMessage } from "../../infra/errors.js";
|
||||
import { createSubsystemLogger } from "../../logging/subsystem.js";
|
||||
import { loadPluginManifestRegistry } from "../../plugins/manifest-registry.js";
|
||||
import {
|
||||
groupPluginDiscoveryProvidersByOrder,
|
||||
normalizePluginDiscoveryResult,
|
||||
@@ -15,11 +14,23 @@ import {
|
||||
resolveBundledProviderCompatPluginIds,
|
||||
resolveOwningPluginIdsForProvider,
|
||||
} from "../../plugins/providers.js";
|
||||
import type { ProviderPlugin } from "../../plugins/types.js";
|
||||
|
||||
const DISCOVERY_ORDERS = ["simple", "profile", "paired", "late"] as const;
|
||||
const SELF_HOSTED_DISCOVERY_PROVIDER_IDS = new Set(["lmstudio", "ollama", "sglang", "vllm"]);
|
||||
const log = createSubsystemLogger("models/list-provider-catalog");
|
||||
|
||||
function providerMatchesFilter(params: {
|
||||
provider: Pick<ProviderPlugin, "id" | "aliases" | "hookAliases">;
|
||||
providerFilter: string;
|
||||
}): boolean {
|
||||
return [
|
||||
params.provider.id,
|
||||
...(params.provider.aliases ?? []),
|
||||
...(params.provider.hookAliases ?? []),
|
||||
].some((providerId) => normalizeProviderId(providerId) === params.providerFilter);
|
||||
}
|
||||
|
||||
export async function resolveProviderCatalogPluginIdsForFilter(params: {
|
||||
cfg: OpenClawConfig;
|
||||
env?: NodeJS.ProcessEnv;
|
||||
@@ -51,13 +62,26 @@ export async function hasProviderStaticCatalogForFilter(params: {
|
||||
env?: NodeJS.ProcessEnv;
|
||||
providerFilter: string;
|
||||
}): Promise<boolean> {
|
||||
const providerFilter = normalizeProviderId(params.providerFilter);
|
||||
if (!providerFilter) {
|
||||
return false;
|
||||
}
|
||||
const pluginIds = await resolveProviderCatalogPluginIdsForFilter(params);
|
||||
if (!pluginIds || pluginIds.length === 0) {
|
||||
return false;
|
||||
}
|
||||
const pluginIdSet = new Set(pluginIds);
|
||||
return loadPluginManifestRegistry({ config: params.cfg, env: params.env }).plugins.some(
|
||||
(plugin) => pluginIdSet.has(plugin.id) && typeof plugin.providerDiscoverySource === "string",
|
||||
const providers = await resolvePluginDiscoveryProviders({
|
||||
config: params.cfg,
|
||||
env: params.env,
|
||||
onlyPluginIds: pluginIds,
|
||||
includeUntrustedWorkspacePlugins: false,
|
||||
requireCompleteDiscoveryEntryCoverage: true,
|
||||
discoveryEntriesOnly: true,
|
||||
});
|
||||
return providers.some(
|
||||
(provider) =>
|
||||
typeof provider.staticCatalog?.run === "function" &&
|
||||
providerMatchesFilter({ provider, providerFilter }),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -71,7 +95,7 @@ function modelFromProviderCatalog(params: {
|
||||
name: params.model.name || params.model.id,
|
||||
provider: params.provider,
|
||||
api: params.model.api ?? params.providerConfig.api ?? "openai-responses",
|
||||
baseUrl: params.providerConfig.baseUrl,
|
||||
baseUrl: params.model.baseUrl ?? params.providerConfig.baseUrl,
|
||||
reasoning: params.model.reasoning,
|
||||
input: params.model.input ?? ["text"],
|
||||
cost: params.model.cost,
|
||||
@@ -122,6 +146,7 @@ export async function loadProviderCatalogModelsForList(params: {
|
||||
onlyPluginIds: scopedPluginIds,
|
||||
includeUntrustedWorkspacePlugins: false,
|
||||
requireCompleteDiscoveryEntryCoverage: params.staticOnly === true,
|
||||
discoveryEntriesOnly: params.staticOnly === true,
|
||||
})
|
||||
).filter(
|
||||
(provider) =>
|
||||
|
||||
@@ -33,6 +33,11 @@ export function modelRowSourcesRequireRegistry(params: {
|
||||
export async function appendAllModelRowSources(params: AllModelRowSources): Promise<void> {
|
||||
if (params.context.filter.provider && params.useProviderCatalogFastPath) {
|
||||
let seenKeys = new Set<string>();
|
||||
appendConfiguredProviderRows({
|
||||
rows: params.rows,
|
||||
context: params.context,
|
||||
seenKeys,
|
||||
});
|
||||
const catalogRows = await appendProviderCatalogRows({
|
||||
rows: params.rows,
|
||||
context: params.context,
|
||||
@@ -46,11 +51,6 @@ export async function appendAllModelRowSources(params: AllModelRowSources): Prom
|
||||
context: params.context,
|
||||
});
|
||||
}
|
||||
appendConfiguredProviderRows({
|
||||
rows: params.rows,
|
||||
context: params.context,
|
||||
seenKeys,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -108,10 +108,7 @@ function shouldListConfiguredProviderModel(params: {
|
||||
providerConfig: Partial<ModelProviderConfig>;
|
||||
model: Partial<ModelDefinitionConfig>;
|
||||
}): boolean {
|
||||
return (
|
||||
params.providerConfig.apiKey !== undefined &&
|
||||
(params.providerConfig.api !== undefined || params.model.api !== undefined)
|
||||
);
|
||||
return params.providerConfig.api !== undefined || params.model.api !== undefined;
|
||||
}
|
||||
|
||||
export function appendDiscoveredRows(params: {
|
||||
|
||||
@@ -44,6 +44,7 @@ function resolveProviderDiscoveryEntryPlugins(params: {
|
||||
onlyPluginIds?: string[];
|
||||
includeUntrustedWorkspacePlugins?: boolean;
|
||||
requireCompleteDiscoveryEntryCoverage?: boolean;
|
||||
discoveryEntriesOnly?: boolean;
|
||||
}): ProviderPlugin[] {
|
||||
const pluginIds = resolveDiscoveredProviderPluginIds(params);
|
||||
const pluginIdSet = new Set(pluginIds);
|
||||
@@ -82,11 +83,15 @@ export function resolvePluginDiscoveryProvidersRuntime(params: {
|
||||
onlyPluginIds?: string[];
|
||||
includeUntrustedWorkspacePlugins?: boolean;
|
||||
requireCompleteDiscoveryEntryCoverage?: boolean;
|
||||
discoveryEntriesOnly?: boolean;
|
||||
}): ProviderPlugin[] {
|
||||
const entryProviders = resolveProviderDiscoveryEntryPlugins(params);
|
||||
if (entryProviders.length > 0) {
|
||||
return entryProviders;
|
||||
}
|
||||
if (params.discoveryEntriesOnly === true) {
|
||||
return [];
|
||||
}
|
||||
return resolvePluginProviders({
|
||||
...params,
|
||||
bundledProviderAllowlistCompat: true,
|
||||
|
||||
@@ -35,6 +35,7 @@ export async function resolvePluginDiscoveryProviders(params: {
|
||||
onlyPluginIds?: string[];
|
||||
includeUntrustedWorkspacePlugins?: boolean;
|
||||
requireCompleteDiscoveryEntryCoverage?: boolean;
|
||||
discoveryEntriesOnly?: boolean;
|
||||
}): Promise<ProviderPlugin[]> {
|
||||
return (await loadProviderRuntime())
|
||||
.resolvePluginDiscoveryProvidersRuntime(params)
|
||||
|
||||
Reference in New Issue
Block a user