mirror of https://github.com/openclaw/openclaw.git
refactor: route bundled provider catalog hooks through plugins
This commit is contained in:
parent
910cb9f1af
commit
570bfb655f
|
|
@ -15,22 +15,22 @@ const PROVIDER_CATALOG_CONTRACT_TIMEOUT_MS = 300_000;
|
|||
type ResolvePluginProviders = typeof import("../providers.runtime.js").resolvePluginProviders;
|
||||
type ResolveOwningPluginIdsForProvider =
|
||||
typeof import("../providers.js").resolveOwningPluginIdsForProvider;
|
||||
type ResolveNonBundledProviderPluginIds =
|
||||
typeof import("../providers.js").resolveNonBundledProviderPluginIds;
|
||||
type ResolveCatalogHookProviderPluginIds =
|
||||
typeof import("../providers.js").resolveCatalogHookProviderPluginIds;
|
||||
|
||||
const resolvePluginProvidersMock = vi.hoisted(() => vi.fn<ResolvePluginProviders>(() => []));
|
||||
const resolveOwningPluginIdsForProviderMock = vi.hoisted(() =>
|
||||
vi.fn<ResolveOwningPluginIdsForProvider>(() => undefined),
|
||||
);
|
||||
const resolveNonBundledProviderPluginIdsMock = vi.hoisted(() =>
|
||||
vi.fn<ResolveNonBundledProviderPluginIds>((_) => [] as string[]),
|
||||
const resolveCatalogHookProviderPluginIdsMock = vi.hoisted(() =>
|
||||
vi.fn<ResolveCatalogHookProviderPluginIds>((_) => [] as string[]),
|
||||
);
|
||||
|
||||
vi.mock("../providers.js", () => ({
|
||||
resolveOwningPluginIdsForProvider: (params: unknown) =>
|
||||
resolveOwningPluginIdsForProviderMock(params as never),
|
||||
resolveNonBundledProviderPluginIds: (params: unknown) =>
|
||||
resolveNonBundledProviderPluginIdsMock(params as never),
|
||||
resolveCatalogHookProviderPluginIds: (params: unknown) =>
|
||||
resolveCatalogHookProviderPluginIdsMock(params as never),
|
||||
}));
|
||||
|
||||
vi.mock("../providers.runtime.js", () => ({
|
||||
|
|
@ -83,8 +83,8 @@ describe("provider catalog contract", { timeout: PROVIDER_CATALOG_CONTRACT_TIMEO
|
|||
}
|
||||
});
|
||||
|
||||
resolveNonBundledProviderPluginIdsMock.mockReset();
|
||||
resolveNonBundledProviderPluginIdsMock.mockReturnValue([]);
|
||||
resolveCatalogHookProviderPluginIdsMock.mockReset();
|
||||
resolveCatalogHookProviderPluginIdsMock.mockReturnValue(["openai"]);
|
||||
});
|
||||
|
||||
it("keeps codex-only missing-auth hints wired through the provider runtime", () => {
|
||||
|
|
|
|||
|
|
@ -1,121 +0,0 @@
|
|||
import { normalizeProviderId } from "../agents/provider-id.js";
|
||||
import type {
|
||||
ProviderAugmentModelCatalogContext,
|
||||
ProviderBuiltInModelSuppressionContext,
|
||||
} from "./types.js";
|
||||
|
||||
const OPENAI_PROVIDER_ID = "openai";
|
||||
const OPENAI_CODEX_PROVIDER_ID = "openai-codex";
|
||||
const OPENAI_DIRECT_SPARK_MODEL_ID = "gpt-5.3-codex-spark";
|
||||
const SUPPRESSED_SPARK_PROVIDERS = new Set(["openai", "azure-openai-responses"]);
|
||||
|
||||
function findCatalogTemplate(params: {
|
||||
entries: ReadonlyArray<{ provider: string; id: string }>;
|
||||
providerId: string;
|
||||
templateIds: readonly string[];
|
||||
}) {
|
||||
return params.templateIds
|
||||
.map((templateId) =>
|
||||
params.entries.find(
|
||||
(entry) =>
|
||||
entry.provider.toLowerCase() === params.providerId.toLowerCase() &&
|
||||
entry.id.toLowerCase() === templateId.toLowerCase(),
|
||||
),
|
||||
)
|
||||
.find((entry) => entry !== undefined);
|
||||
}
|
||||
|
||||
export function resolveBundledProviderBuiltInModelSuppression(
|
||||
context: ProviderBuiltInModelSuppressionContext,
|
||||
) {
|
||||
if (
|
||||
!SUPPRESSED_SPARK_PROVIDERS.has(normalizeProviderId(context.provider)) ||
|
||||
context.modelId.toLowerCase() !== OPENAI_DIRECT_SPARK_MODEL_ID
|
||||
) {
|
||||
return undefined;
|
||||
}
|
||||
return {
|
||||
suppress: true,
|
||||
errorMessage: `Unknown model: ${context.provider}/${OPENAI_DIRECT_SPARK_MODEL_ID}. ${OPENAI_DIRECT_SPARK_MODEL_ID} is only supported via openai-codex OAuth. Use openai-codex/${OPENAI_DIRECT_SPARK_MODEL_ID}.`,
|
||||
};
|
||||
}
|
||||
|
||||
export function augmentBundledProviderCatalog(
|
||||
context: ProviderAugmentModelCatalogContext,
|
||||
): ProviderAugmentModelCatalogContext["entries"] {
|
||||
const openAiGpt54Template = findCatalogTemplate({
|
||||
entries: context.entries,
|
||||
providerId: OPENAI_PROVIDER_ID,
|
||||
templateIds: ["gpt-5.2"],
|
||||
});
|
||||
const openAiGpt54ProTemplate = findCatalogTemplate({
|
||||
entries: context.entries,
|
||||
providerId: OPENAI_PROVIDER_ID,
|
||||
templateIds: ["gpt-5.2-pro", "gpt-5.2"],
|
||||
});
|
||||
const openAiGpt54MiniTemplate = findCatalogTemplate({
|
||||
entries: context.entries,
|
||||
providerId: OPENAI_PROVIDER_ID,
|
||||
templateIds: ["gpt-5-mini"],
|
||||
});
|
||||
const openAiGpt54NanoTemplate = findCatalogTemplate({
|
||||
entries: context.entries,
|
||||
providerId: OPENAI_PROVIDER_ID,
|
||||
templateIds: ["gpt-5-nano", "gpt-5-mini"],
|
||||
});
|
||||
const openAiCodexGpt54Template = findCatalogTemplate({
|
||||
entries: context.entries,
|
||||
providerId: OPENAI_CODEX_PROVIDER_ID,
|
||||
templateIds: ["gpt-5.4", "gpt-5.3-codex", "gpt-5.2-codex"],
|
||||
});
|
||||
const openAiCodexSparkTemplate = findCatalogTemplate({
|
||||
entries: context.entries,
|
||||
providerId: OPENAI_CODEX_PROVIDER_ID,
|
||||
templateIds: ["gpt-5.4", "gpt-5.3-codex", "gpt-5.2-codex"],
|
||||
});
|
||||
|
||||
return [
|
||||
openAiGpt54Template
|
||||
? {
|
||||
...openAiGpt54Template,
|
||||
id: "gpt-5.4",
|
||||
name: "gpt-5.4",
|
||||
}
|
||||
: undefined,
|
||||
openAiGpt54ProTemplate
|
||||
? {
|
||||
...openAiGpt54ProTemplate,
|
||||
id: "gpt-5.4-pro",
|
||||
name: "gpt-5.4-pro",
|
||||
}
|
||||
: undefined,
|
||||
openAiGpt54MiniTemplate
|
||||
? {
|
||||
...openAiGpt54MiniTemplate,
|
||||
id: "gpt-5.4-mini",
|
||||
name: "gpt-5.4-mini",
|
||||
}
|
||||
: undefined,
|
||||
openAiGpt54NanoTemplate
|
||||
? {
|
||||
...openAiGpt54NanoTemplate,
|
||||
id: "gpt-5.4-nano",
|
||||
name: "gpt-5.4-nano",
|
||||
}
|
||||
: undefined,
|
||||
openAiCodexGpt54Template
|
||||
? {
|
||||
...openAiCodexGpt54Template,
|
||||
id: "gpt-5.4",
|
||||
name: "gpt-5.4",
|
||||
}
|
||||
: undefined,
|
||||
openAiCodexSparkTemplate
|
||||
? {
|
||||
...openAiCodexSparkTemplate,
|
||||
id: OPENAI_DIRECT_SPARK_MODEL_ID,
|
||||
name: OPENAI_DIRECT_SPARK_MODEL_ID,
|
||||
}
|
||||
: undefined,
|
||||
].filter((entry): entry is NonNullable<typeof entry> => entry !== undefined);
|
||||
}
|
||||
|
|
@ -7,13 +7,13 @@ import {
|
|||
import type { ProviderPlugin, ProviderRuntimeModel } from "./types.js";
|
||||
|
||||
type ResolvePluginProviders = typeof import("./providers.runtime.js").resolvePluginProviders;
|
||||
type ResolveNonBundledProviderPluginIds =
|
||||
typeof import("./providers.js").resolveNonBundledProviderPluginIds;
|
||||
type ResolveCatalogHookProviderPluginIds =
|
||||
typeof import("./providers.js").resolveCatalogHookProviderPluginIds;
|
||||
type ResolveOwningPluginIdsForProvider =
|
||||
typeof import("./providers.js").resolveOwningPluginIdsForProvider;
|
||||
|
||||
const resolvePluginProvidersMock = vi.fn<ResolvePluginProviders>((_) => [] as ProviderPlugin[]);
|
||||
const resolveNonBundledProviderPluginIdsMock = vi.fn<ResolveNonBundledProviderPluginIds>(
|
||||
const resolveCatalogHookProviderPluginIdsMock = vi.fn<ResolveCatalogHookProviderPluginIds>(
|
||||
(_) => [] as string[],
|
||||
);
|
||||
const resolveOwningPluginIdsForProviderMock = vi.fn<ResolveOwningPluginIdsForProvider>(
|
||||
|
|
@ -64,8 +64,8 @@ describe("provider-runtime", () => {
|
|||
beforeEach(async () => {
|
||||
vi.resetModules();
|
||||
vi.doMock("./providers.js", () => ({
|
||||
resolveNonBundledProviderPluginIds: (params: unknown) =>
|
||||
resolveNonBundledProviderPluginIdsMock(params as never),
|
||||
resolveCatalogHookProviderPluginIds: (params: unknown) =>
|
||||
resolveCatalogHookProviderPluginIdsMock(params as never),
|
||||
resolveOwningPluginIdsForProvider: (params: unknown) =>
|
||||
resolveOwningPluginIdsForProviderMock(params as never),
|
||||
}));
|
||||
|
|
@ -103,8 +103,8 @@ describe("provider-runtime", () => {
|
|||
resetProviderRuntimeHookCacheForTest();
|
||||
resolvePluginProvidersMock.mockReset();
|
||||
resolvePluginProvidersMock.mockReturnValue([]);
|
||||
resolveNonBundledProviderPluginIdsMock.mockReset();
|
||||
resolveNonBundledProviderPluginIdsMock.mockReturnValue([]);
|
||||
resolveCatalogHookProviderPluginIdsMock.mockReset();
|
||||
resolveCatalogHookProviderPluginIdsMock.mockReturnValue([]);
|
||||
resolveOwningPluginIdsForProviderMock.mockReset();
|
||||
resolveOwningPluginIdsForProviderMock.mockReturnValue(undefined);
|
||||
});
|
||||
|
|
@ -150,6 +150,7 @@ describe("provider-runtime", () => {
|
|||
});
|
||||
|
||||
it("dispatches runtime hooks for the matched provider", async () => {
|
||||
resolveCatalogHookProviderPluginIdsMock.mockReturnValue(["openai"]);
|
||||
resolveOwningPluginIdsForProviderMock.mockImplementation((params) => {
|
||||
if (params.provider === "demo") {
|
||||
return ["demo"];
|
||||
|
|
@ -248,6 +249,8 @@ describe("provider-runtime", () => {
|
|||
augmentModelCatalog: () => [
|
||||
{ provider: "openai", id: "gpt-5.4", name: "gpt-5.4" },
|
||||
{ provider: "openai", id: "gpt-5.4-pro", name: "gpt-5.4-pro" },
|
||||
{ provider: "openai", id: "gpt-5.4-mini", name: "gpt-5.4-mini" },
|
||||
{ provider: "openai", id: "gpt-5.4-nano", name: "gpt-5.4-nano" },
|
||||
{ provider: "openai-codex", id: "gpt-5.4", name: "gpt-5.4" },
|
||||
{
|
||||
provider: "openai-codex",
|
||||
|
|
@ -540,7 +543,40 @@ describe("provider-runtime", () => {
|
|||
expect(fetchUsageSnapshot).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it("resolves bundled catalog hooks without loading provider plugins", async () => {
|
||||
it("resolves bundled catalog hooks through provider plugins", async () => {
|
||||
resolveCatalogHookProviderPluginIdsMock.mockReturnValue(["openai"]);
|
||||
resolvePluginProvidersMock.mockImplementation((params?: { onlyPluginIds?: string[] }) => {
|
||||
const onlyPluginIds = params?.onlyPluginIds;
|
||||
if (!onlyPluginIds || !onlyPluginIds.includes("openai")) {
|
||||
return [];
|
||||
}
|
||||
return [
|
||||
{
|
||||
id: "openai",
|
||||
label: "OpenAI",
|
||||
auth: [],
|
||||
suppressBuiltInModel: ({ provider, modelId }) =>
|
||||
provider === "openai" && modelId === "gpt-5.3-codex-spark"
|
||||
? { suppress: true, errorMessage: "openai-codex/gpt-5.3-codex-spark" }
|
||||
: provider === "azure-openai-responses" && modelId === "gpt-5.3-codex-spark"
|
||||
? { suppress: true, errorMessage: "openai-codex/gpt-5.3-codex-spark" }
|
||||
: undefined,
|
||||
augmentModelCatalog: () => [
|
||||
{ provider: "openai", id: "gpt-5.4", name: "gpt-5.4" },
|
||||
{ provider: "openai", id: "gpt-5.4-pro", name: "gpt-5.4-pro" },
|
||||
{ provider: "openai", id: "gpt-5.4-mini", name: "gpt-5.4-mini" },
|
||||
{ provider: "openai", id: "gpt-5.4-nano", name: "gpt-5.4-nano" },
|
||||
{ provider: "openai-codex", id: "gpt-5.4", name: "gpt-5.4" },
|
||||
{
|
||||
provider: "openai-codex",
|
||||
id: "gpt-5.3-codex-spark",
|
||||
name: "gpt-5.3-codex-spark",
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
});
|
||||
|
||||
expect(
|
||||
resolveProviderBuiltInModelSuppression({
|
||||
env: process.env,
|
||||
|
|
@ -581,6 +617,12 @@ describe("provider-runtime", () => {
|
|||
},
|
||||
]);
|
||||
|
||||
expect(resolvePluginProvidersMock).not.toHaveBeenCalled();
|
||||
expect(resolvePluginProvidersMock).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
onlyPluginIds: ["openai"],
|
||||
activate: false,
|
||||
cache: false,
|
||||
}),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -2,11 +2,7 @@ import type { AuthProfileCredential, OAuthCredential } from "../agents/auth-prof
|
|||
import { normalizeProviderId } from "../agents/provider-id.js";
|
||||
import type { OpenClawConfig } from "../config/config.js";
|
||||
import {
|
||||
augmentBundledProviderCatalog,
|
||||
resolveBundledProviderBuiltInModelSuppression,
|
||||
} from "./provider-catalog-metadata.js";
|
||||
import {
|
||||
resolveNonBundledProviderPluginIds,
|
||||
resolveCatalogHookProviderPluginIds,
|
||||
resolveOwningPluginIdsForProvider,
|
||||
} from "./providers.js";
|
||||
import { resolvePluginProviders } from "./providers.runtime.js";
|
||||
|
|
@ -145,7 +141,7 @@ function resolveProviderPluginsForCatalogHooks(params: {
|
|||
workspaceDir?: string;
|
||||
env?: NodeJS.ProcessEnv;
|
||||
}): ProviderPlugin[] {
|
||||
const onlyPluginIds = resolveNonBundledProviderPluginIds({
|
||||
const onlyPluginIds = resolveCatalogHookProviderPluginIds({
|
||||
config: params.config,
|
||||
workspaceDir: params.workspaceDir,
|
||||
env: params.env,
|
||||
|
|
@ -416,10 +412,6 @@ export function resolveProviderBuiltInModelSuppression(params: {
|
|||
env?: NodeJS.ProcessEnv;
|
||||
context: ProviderBuiltInModelSuppressionContext;
|
||||
}) {
|
||||
const bundledResult = resolveBundledProviderBuiltInModelSuppression(params.context);
|
||||
if (bundledResult?.suppress) {
|
||||
return bundledResult;
|
||||
}
|
||||
for (const plugin of resolveProviderPluginsForCatalogHooks(params)) {
|
||||
const result = plugin.suppressBuiltInModel?.(params.context);
|
||||
if (result?.suppress) {
|
||||
|
|
@ -435,9 +427,7 @@ export async function augmentModelCatalogWithProviderPlugins(params: {
|
|||
env?: NodeJS.ProcessEnv;
|
||||
context: ProviderAugmentModelCatalogContext;
|
||||
}) {
|
||||
const supplemental = [
|
||||
...augmentBundledProviderCatalog(params.context),
|
||||
] as ProviderAugmentModelCatalogContext["entries"];
|
||||
const supplemental = [] as ProviderAugmentModelCatalogContext["entries"];
|
||||
for (const plugin of resolveProviderPluginsForCatalogHooks(params)) {
|
||||
const next = await plugin.augmentModelCatalog?.(params.context);
|
||||
if (!next || next.length === 0) {
|
||||
|
|
|
|||
|
|
@ -91,3 +91,32 @@ export function resolveNonBundledProviderPluginIds(params: {
|
|||
.map((plugin) => plugin.id)
|
||||
.toSorted((left, right) => left.localeCompare(right));
|
||||
}
|
||||
|
||||
export function resolveCatalogHookProviderPluginIds(params: {
|
||||
config?: PluginLoadOptions["config"];
|
||||
workspaceDir?: string;
|
||||
env?: PluginLoadOptions["env"];
|
||||
}): string[] {
|
||||
const registry = loadPluginManifestRegistry({
|
||||
config: params.config,
|
||||
workspaceDir: params.workspaceDir,
|
||||
env: params.env,
|
||||
});
|
||||
const normalizedConfig = normalizePluginsConfig(params.config?.plugins);
|
||||
const enabledProviderPluginIds = registry.plugins
|
||||
.filter(
|
||||
(plugin) =>
|
||||
plugin.providers.length > 0 &&
|
||||
resolveEffectiveEnableState({
|
||||
id: plugin.id,
|
||||
origin: plugin.origin,
|
||||
config: normalizedConfig,
|
||||
rootConfig: params.config,
|
||||
}).enabled,
|
||||
)
|
||||
.map((plugin) => plugin.id);
|
||||
const bundledCompatPluginIds = resolveBundledProviderCompatPluginIds(params);
|
||||
return [...new Set([...enabledProviderPluginIds, ...bundledCompatPluginIds])].toSorted(
|
||||
(left, right) => left.localeCompare(right),
|
||||
);
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue