mirror of
https://github.com/openclaw/openclaw.git
synced 2026-04-08 07:41:08 +00:00
feat(plugins): auto-load provider plugins from model support
This commit is contained in:
@@ -15,6 +15,7 @@ import {
|
||||
type PluginManifest,
|
||||
type PluginManifestChannelConfig,
|
||||
type PluginManifestContracts,
|
||||
type PluginManifestModelSupport,
|
||||
} from "./manifest.js";
|
||||
import { checkMinHostVersion } from "./min-host-version.js";
|
||||
import { isPathInside, safeRealpathSync } from "./path-safety.js";
|
||||
@@ -56,6 +57,7 @@ export type PluginManifestRecord = {
|
||||
kind?: PluginKind | PluginKind[];
|
||||
channels: string[];
|
||||
providers: string[];
|
||||
modelSupport?: PluginManifestModelSupport;
|
||||
cliBackends: string[];
|
||||
providerAuthEnvVars?: Record<string, string[]>;
|
||||
providerAuthChoices?: PluginManifest["providerAuthChoices"];
|
||||
@@ -216,6 +218,7 @@ function buildRecord(params: {
|
||||
kind: params.manifest.kind,
|
||||
channels: params.manifest.channels ?? [],
|
||||
providers: params.manifest.providers ?? [],
|
||||
modelSupport: params.manifest.modelSupport,
|
||||
cliBackends: params.manifest.cliBackends ?? [],
|
||||
providerAuthEnvVars: params.manifest.providerAuthEnvVars,
|
||||
providerAuthChoices: params.manifest.providerAuthChoices,
|
||||
|
||||
@@ -81,6 +81,27 @@ describe("loadPluginManifest JSON5 tolerance", () => {
|
||||
}
|
||||
});
|
||||
|
||||
it("normalizes modelSupport metadata from the manifest", () => {
|
||||
const dir = makeTempDir();
|
||||
const json5Content = `{
|
||||
id: "provider-plugin",
|
||||
modelSupport: {
|
||||
modelPrefixes: ["gpt-", "", "claude-"],
|
||||
modelPatterns: ["^o[0-9].*", ""],
|
||||
},
|
||||
configSchema: { type: "object" }
|
||||
}`;
|
||||
fs.writeFileSync(path.join(dir, "openclaw.plugin.json"), json5Content, "utf-8");
|
||||
const result = loadPluginManifest(dir, false);
|
||||
expect(result.ok).toBe(true);
|
||||
if (result.ok) {
|
||||
expect(result.manifest.modelSupport).toEqual({
|
||||
modelPrefixes: ["gpt-", "claude-"],
|
||||
modelPatterns: ["^o[0-9].*"],
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
it("still rejects completely invalid syntax", () => {
|
||||
const dir = makeTempDir();
|
||||
fs.writeFileSync(path.join(dir, "openclaw.plugin.json"), "not json at all {{{}}", "utf-8");
|
||||
|
||||
@@ -17,6 +17,19 @@ export type PluginManifestChannelConfig = {
|
||||
preferOver?: string[];
|
||||
};
|
||||
|
||||
export type PluginManifestModelSupport = {
|
||||
/**
|
||||
* Cheap manifest-owned model-id prefixes for transparent provider activation
|
||||
* from shorthand model refs such as `gpt-5.4` or `claude-sonnet-4.6`.
|
||||
*/
|
||||
modelPrefixes?: string[];
|
||||
/**
|
||||
* Regex sources matched against the raw model id after profile suffixes are
|
||||
* stripped. Use this when simple prefixes are not expressive enough.
|
||||
*/
|
||||
modelPatterns?: string[];
|
||||
};
|
||||
|
||||
export type PluginManifest = {
|
||||
id: string;
|
||||
configSchema: Record<string, unknown>;
|
||||
@@ -28,6 +41,11 @@ export type PluginManifest = {
|
||||
kind?: PluginKind | PluginKind[];
|
||||
channels?: string[];
|
||||
providers?: string[];
|
||||
/**
|
||||
* Cheap model-family ownership metadata used before plugin runtime loads.
|
||||
* Use this for shorthand model refs that omit an explicit provider prefix.
|
||||
*/
|
||||
modelSupport?: PluginManifestModelSupport;
|
||||
/** Cheap startup activation lookup for plugin-owned CLI inference backends. */
|
||||
cliBackends?: string[];
|
||||
/** Cheap provider-auth env lookup without booting plugin runtime. */
|
||||
@@ -148,6 +166,21 @@ function normalizeManifestContracts(value: unknown): PluginManifestContracts | u
|
||||
return Object.keys(contracts).length > 0 ? contracts : undefined;
|
||||
}
|
||||
|
||||
function normalizeManifestModelSupport(value: unknown): PluginManifestModelSupport | undefined {
|
||||
if (!isRecord(value)) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const modelPrefixes = normalizeStringList(value.modelPrefixes);
|
||||
const modelPatterns = normalizeStringList(value.modelPatterns);
|
||||
const modelSupport = {
|
||||
...(modelPrefixes.length > 0 ? { modelPrefixes } : {}),
|
||||
...(modelPatterns.length > 0 ? { modelPatterns } : {}),
|
||||
} satisfies PluginManifestModelSupport;
|
||||
|
||||
return Object.keys(modelSupport).length > 0 ? modelSupport : undefined;
|
||||
}
|
||||
|
||||
function normalizeProviderAuthChoices(
|
||||
value: unknown,
|
||||
): PluginManifestProviderAuthChoice[] | undefined {
|
||||
@@ -313,6 +346,7 @@ export function loadPluginManifest(
|
||||
const version = typeof raw.version === "string" ? raw.version.trim() : undefined;
|
||||
const channels = normalizeStringList(raw.channels);
|
||||
const providers = normalizeStringList(raw.providers);
|
||||
const modelSupport = normalizeManifestModelSupport(raw.modelSupport);
|
||||
const cliBackends = normalizeStringList(raw.cliBackends);
|
||||
const providerAuthEnvVars = normalizeStringListRecord(raw.providerAuthEnvVars);
|
||||
const providerAuthChoices = normalizeProviderAuthChoices(raw.providerAuthChoices);
|
||||
@@ -338,6 +372,7 @@ export function loadPluginManifest(
|
||||
kind,
|
||||
channels,
|
||||
providers,
|
||||
modelSupport,
|
||||
cliBackends,
|
||||
providerAuthEnvVars,
|
||||
providerAuthChoices,
|
||||
|
||||
@@ -5,12 +5,45 @@ import { createPluginLoaderLogger } from "./logger.js";
|
||||
import {
|
||||
resolveEnabledProviderPluginIds,
|
||||
resolveBundledProviderCompatPluginIds,
|
||||
resolveOwningPluginIdsForModelRefs,
|
||||
withBundledProviderVitestCompat,
|
||||
} from "./providers.js";
|
||||
import type { ProviderPlugin } from "./types.js";
|
||||
|
||||
const log = createSubsystemLogger("plugins");
|
||||
|
||||
function withRuntimeActivatedPluginIds(params: {
|
||||
config?: PluginLoadOptions["config"];
|
||||
pluginIds: readonly string[];
|
||||
}): PluginLoadOptions["config"] {
|
||||
if (params.pluginIds.length === 0) {
|
||||
return params.config;
|
||||
}
|
||||
const allow = new Set(params.config?.plugins?.allow ?? []);
|
||||
const entries = {
|
||||
...params.config?.plugins?.entries,
|
||||
};
|
||||
for (const pluginId of params.pluginIds) {
|
||||
const normalized = pluginId.trim();
|
||||
if (!normalized) {
|
||||
continue;
|
||||
}
|
||||
allow.add(normalized);
|
||||
entries[normalized] = {
|
||||
...entries[normalized],
|
||||
enabled: true,
|
||||
};
|
||||
}
|
||||
return {
|
||||
...params.config,
|
||||
plugins: {
|
||||
...params.config?.plugins,
|
||||
...(allow.size > 0 ? { allow: [...allow] } : {}),
|
||||
entries,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export function resolvePluginProviders(params: {
|
||||
config?: PluginLoadOptions["config"];
|
||||
workspaceDir?: string;
|
||||
@@ -19,16 +52,33 @@ export function resolvePluginProviders(params: {
|
||||
bundledProviderAllowlistCompat?: boolean;
|
||||
bundledProviderVitestCompat?: boolean;
|
||||
onlyPluginIds?: string[];
|
||||
modelRefs?: readonly string[];
|
||||
activate?: boolean;
|
||||
cache?: boolean;
|
||||
pluginSdkResolution?: PluginLoadOptions["pluginSdkResolution"];
|
||||
}): ProviderPlugin[] {
|
||||
const env = params.env ?? process.env;
|
||||
const modelOwnedPluginIds = params.modelRefs?.length
|
||||
? resolveOwningPluginIdsForModelRefs({
|
||||
models: params.modelRefs,
|
||||
config: params.config,
|
||||
workspaceDir: params.workspaceDir,
|
||||
env,
|
||||
})
|
||||
: [];
|
||||
const requestedPluginIds =
|
||||
params.onlyPluginIds || modelOwnedPluginIds.length > 0
|
||||
? [...new Set([...(params.onlyPluginIds ?? []), ...modelOwnedPluginIds])]
|
||||
: undefined;
|
||||
const runtimeConfig = withRuntimeActivatedPluginIds({
|
||||
config: params.config,
|
||||
pluginIds: modelOwnedPluginIds,
|
||||
});
|
||||
const activation = resolveBundledPluginCompatibleActivationInputs({
|
||||
rawConfig: params.config,
|
||||
rawConfig: runtimeConfig,
|
||||
env,
|
||||
workspaceDir: params.workspaceDir,
|
||||
onlyPluginIds: params.onlyPluginIds,
|
||||
onlyPluginIds: requestedPluginIds,
|
||||
applyAutoEnable: true,
|
||||
compatMode: {
|
||||
allowlist: params.bundledProviderAllowlistCompat,
|
||||
@@ -48,7 +98,7 @@ export function resolvePluginProviders(params: {
|
||||
config,
|
||||
workspaceDir: params.workspaceDir,
|
||||
env,
|
||||
onlyPluginIds: params.onlyPluginIds,
|
||||
onlyPluginIds: requestedPluginIds,
|
||||
});
|
||||
const registry = resolveRuntimePluginRegistry({
|
||||
config,
|
||||
|
||||
@@ -15,18 +15,21 @@ const loadPluginManifestRegistryMock = vi.fn<LoadPluginManifestRegistry>();
|
||||
const applyPluginAutoEnableMock = vi.fn<ApplyPluginAutoEnable>();
|
||||
|
||||
let resolveOwningPluginIdsForProvider: typeof import("./providers.js").resolveOwningPluginIdsForProvider;
|
||||
let resolveOwningPluginIdsForModelRef: typeof import("./providers.js").resolveOwningPluginIdsForModelRef;
|
||||
let resolvePluginProviders: typeof import("./providers.runtime.js").resolvePluginProviders;
|
||||
|
||||
function createManifestProviderPlugin(params: {
|
||||
id: string;
|
||||
providerIds: string[];
|
||||
origin?: "bundled" | "workspace";
|
||||
modelSupport?: { modelPrefixes?: string[]; modelPatterns?: string[] };
|
||||
}): PluginManifestRecord {
|
||||
return {
|
||||
id: params.id,
|
||||
channels: [],
|
||||
cliBackends: [],
|
||||
providers: params.providerIds,
|
||||
modelSupport: params.modelSupport,
|
||||
skills: [],
|
||||
hooks: [],
|
||||
origin: params.origin ?? "bundled",
|
||||
@@ -52,6 +55,47 @@ function setOwningProviderManifestPlugins() {
|
||||
createManifestProviderPlugin({
|
||||
id: "openai",
|
||||
providerIds: ["openai", "openai-codex"],
|
||||
modelSupport: {
|
||||
modelPrefixes: ["gpt-", "o1", "o3", "o4"],
|
||||
},
|
||||
}),
|
||||
createManifestProviderPlugin({
|
||||
id: "anthropic",
|
||||
providerIds: ["anthropic"],
|
||||
modelSupport: {
|
||||
modelPrefixes: ["claude-"],
|
||||
},
|
||||
}),
|
||||
]);
|
||||
}
|
||||
|
||||
function setOwningProviderManifestPluginsWithWorkspace() {
|
||||
setManifestPlugins([
|
||||
createManifestProviderPlugin({
|
||||
id: "minimax",
|
||||
providerIds: ["minimax", "minimax-portal"],
|
||||
}),
|
||||
createManifestProviderPlugin({
|
||||
id: "openai",
|
||||
providerIds: ["openai", "openai-codex"],
|
||||
modelSupport: {
|
||||
modelPrefixes: ["gpt-", "o1", "o3", "o4"],
|
||||
},
|
||||
}),
|
||||
createManifestProviderPlugin({
|
||||
id: "anthropic",
|
||||
providerIds: ["anthropic"],
|
||||
modelSupport: {
|
||||
modelPrefixes: ["claude-"],
|
||||
},
|
||||
}),
|
||||
createManifestProviderPlugin({
|
||||
id: "workspace-provider",
|
||||
providerIds: ["workspace-provider"],
|
||||
origin: "workspace",
|
||||
modelSupport: {
|
||||
modelPrefixes: ["workspace-model-"],
|
||||
},
|
||||
}),
|
||||
]);
|
||||
}
|
||||
@@ -158,6 +202,10 @@ function expectOwningPluginIds(provider: string, expectedPluginIds?: readonly st
|
||||
expect(resolveOwningPluginIdsForProvider({ provider })).toEqual(expectedPluginIds);
|
||||
}
|
||||
|
||||
function expectModelOwningPluginIds(model: string, expectedPluginIds?: readonly string[]) {
|
||||
expect(resolveOwningPluginIdsForModelRef({ model })).toEqual(expectedPluginIds);
|
||||
}
|
||||
|
||||
function expectProviderRuntimeRegistryLoad(params?: { config?: unknown; env?: NodeJS.ProcessEnv }) {
|
||||
expect(resolveRuntimePluginRegistryMock).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
@@ -182,7 +230,8 @@ describe("resolvePluginProviders", () => {
|
||||
loadPluginManifestRegistry: (...args: Parameters<LoadPluginManifestRegistry>) =>
|
||||
loadPluginManifestRegistryMock(...args),
|
||||
}));
|
||||
({ resolveOwningPluginIdsForProvider } = await import("./providers.js"));
|
||||
({ resolveOwningPluginIdsForProvider, resolveOwningPluginIdsForModelRef } =
|
||||
await import("./providers.js"));
|
||||
({ resolvePluginProviders } = await import("./providers.runtime.js"));
|
||||
});
|
||||
|
||||
@@ -215,6 +264,9 @@ describe("resolvePluginProviders", () => {
|
||||
id: "workspace-provider",
|
||||
providerIds: ["workspace-provider"],
|
||||
origin: "workspace",
|
||||
modelSupport: {
|
||||
modelPrefixes: ["workspace-model-"],
|
||||
},
|
||||
}),
|
||||
]);
|
||||
});
|
||||
@@ -433,4 +485,112 @@ describe("resolvePluginProviders", () => {
|
||||
expectOwningPluginIds(provider, expectedPluginIds);
|
||||
},
|
||||
);
|
||||
|
||||
it.each([
|
||||
{
|
||||
model: "gpt-5.4",
|
||||
expectedPluginIds: ["openai"],
|
||||
},
|
||||
{
|
||||
model: "claude-sonnet-4-6",
|
||||
expectedPluginIds: ["anthropic"],
|
||||
},
|
||||
{
|
||||
model: "openai/gpt-5.4",
|
||||
expectedPluginIds: ["openai"],
|
||||
},
|
||||
{
|
||||
model: "workspace-model-fast",
|
||||
expectedPluginIds: ["workspace-provider"],
|
||||
},
|
||||
{
|
||||
model: "unknown-model",
|
||||
expectedPluginIds: undefined,
|
||||
},
|
||||
] as const)(
|
||||
"maps $model to owning plugin ids via modelSupport",
|
||||
({ model, expectedPluginIds }) => {
|
||||
setOwningProviderManifestPluginsWithWorkspace();
|
||||
|
||||
expectModelOwningPluginIds(model, expectedPluginIds);
|
||||
},
|
||||
);
|
||||
|
||||
it("refuses ambiguous bundled shorthand model ownership", () => {
|
||||
setManifestPlugins([
|
||||
createManifestProviderPlugin({
|
||||
id: "openai",
|
||||
providerIds: ["openai"],
|
||||
modelSupport: { modelPrefixes: ["gpt-"] },
|
||||
}),
|
||||
createManifestProviderPlugin({
|
||||
id: "proxy-openai",
|
||||
providerIds: ["proxy-openai"],
|
||||
modelSupport: { modelPrefixes: ["gpt-"] },
|
||||
}),
|
||||
]);
|
||||
|
||||
expectModelOwningPluginIds("gpt-5.4", undefined);
|
||||
});
|
||||
|
||||
it("prefers non-bundled shorthand model ownership over bundled matches", () => {
|
||||
setManifestPlugins([
|
||||
createManifestProviderPlugin({
|
||||
id: "openai",
|
||||
providerIds: ["openai"],
|
||||
modelSupport: { modelPrefixes: ["gpt-"] },
|
||||
}),
|
||||
createManifestProviderPlugin({
|
||||
id: "workspace-openai",
|
||||
providerIds: ["workspace-openai"],
|
||||
origin: "workspace",
|
||||
modelSupport: { modelPrefixes: ["gpt-"] },
|
||||
}),
|
||||
]);
|
||||
|
||||
expectModelOwningPluginIds("gpt-5.4", ["workspace-openai"]);
|
||||
});
|
||||
|
||||
it("auto-loads a model-owned provider plugin from shorthand model refs", () => {
|
||||
setManifestPlugins([
|
||||
createManifestProviderPlugin({
|
||||
id: "openai",
|
||||
providerIds: ["openai", "openai-codex"],
|
||||
modelSupport: {
|
||||
modelPrefixes: ["gpt-", "o1", "o3", "o4"],
|
||||
},
|
||||
}),
|
||||
]);
|
||||
const provider: ProviderPlugin = {
|
||||
id: "openai",
|
||||
label: "OpenAI",
|
||||
auth: [],
|
||||
};
|
||||
const registry = createEmptyPluginRegistry();
|
||||
registry.providers.push({ pluginId: "openai", provider, source: "bundled" });
|
||||
resolveRuntimePluginRegistryMock.mockReturnValue(registry);
|
||||
|
||||
const providers = resolvePluginProviders({
|
||||
config: {},
|
||||
modelRefs: ["gpt-5.4"],
|
||||
bundledProviderAllowlistCompat: true,
|
||||
});
|
||||
|
||||
expectResolvedProviders(providers, [
|
||||
{ id: "openai", label: "OpenAI", auth: [], pluginId: "openai" },
|
||||
]);
|
||||
expect(resolveRuntimePluginRegistryMock).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
onlyPluginIds: ["openai"],
|
||||
config: expect.objectContaining({
|
||||
plugins: expect.objectContaining({
|
||||
allow: ["openai"],
|
||||
entries: {
|
||||
openai: { enabled: true },
|
||||
},
|
||||
}),
|
||||
}),
|
||||
}),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -2,7 +2,11 @@ import { normalizeProviderId } from "../agents/provider-id.js";
|
||||
import { withBundledPluginVitestCompat } from "./bundled-compat.js";
|
||||
import { normalizePluginsConfig, resolveEffectivePluginActivationState } from "./config-state.js";
|
||||
import type { PluginLoadOptions } from "./loader.js";
|
||||
import { loadPluginManifestRegistry } from "./manifest-registry.js";
|
||||
import {
|
||||
loadPluginManifestRegistry,
|
||||
type PluginManifestRecord,
|
||||
type PluginManifestRegistry,
|
||||
} from "./manifest-registry.js";
|
||||
|
||||
export function withBundledProviderVitestCompat(params: {
|
||||
config: PluginLoadOptions["config"];
|
||||
@@ -70,22 +74,112 @@ export const __testing = {
|
||||
withBundledProviderVitestCompat,
|
||||
} as const;
|
||||
|
||||
type ModelSupportMatchKind = "pattern" | "prefix";
|
||||
|
||||
function resolveManifestRegistry(params: {
|
||||
config?: PluginLoadOptions["config"];
|
||||
workspaceDir?: string;
|
||||
env?: PluginLoadOptions["env"];
|
||||
manifestRegistry?: PluginManifestRegistry;
|
||||
}): PluginManifestRegistry {
|
||||
return (
|
||||
params.manifestRegistry ??
|
||||
loadPluginManifestRegistry({
|
||||
config: params.config,
|
||||
workspaceDir: params.workspaceDir,
|
||||
env: params.env,
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
function stripModelProfileSuffix(value: string): string {
|
||||
const trimmed = value.trim();
|
||||
const at = trimmed.indexOf("@");
|
||||
return at <= 0 ? trimmed : trimmed.slice(0, at).trim();
|
||||
}
|
||||
|
||||
function splitExplicitModelRef(rawModel: string): { provider?: string; modelId: string } | null {
|
||||
const trimmed = rawModel.trim();
|
||||
if (!trimmed) {
|
||||
return null;
|
||||
}
|
||||
const slash = trimmed.indexOf("/");
|
||||
if (slash === -1) {
|
||||
const modelId = stripModelProfileSuffix(trimmed);
|
||||
return modelId ? { modelId } : null;
|
||||
}
|
||||
const provider = normalizeProviderId(trimmed.slice(0, slash));
|
||||
const modelId = stripModelProfileSuffix(trimmed.slice(slash + 1));
|
||||
if (!provider || !modelId) {
|
||||
return null;
|
||||
}
|
||||
return { provider, modelId };
|
||||
}
|
||||
|
||||
function resolveModelSupportMatchKind(
|
||||
plugin: PluginManifestRecord,
|
||||
modelId: string,
|
||||
): ModelSupportMatchKind | undefined {
|
||||
const patterns = plugin.modelSupport?.modelPatterns ?? [];
|
||||
for (const patternSource of patterns) {
|
||||
try {
|
||||
if (new RegExp(patternSource, "u").test(modelId)) {
|
||||
return "pattern";
|
||||
}
|
||||
} catch {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
const prefixes = plugin.modelSupport?.modelPrefixes ?? [];
|
||||
for (const prefix of prefixes) {
|
||||
if (modelId.startsWith(prefix)) {
|
||||
return "prefix";
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function dedupeSortedPluginIds(values: Iterable<string>): string[] {
|
||||
return [...new Set(values)].toSorted((left, right) => left.localeCompare(right));
|
||||
}
|
||||
|
||||
function resolvePreferredManifestPluginIds(
|
||||
registry: PluginManifestRegistry,
|
||||
matchedPluginIds: readonly string[],
|
||||
): string[] | undefined {
|
||||
if (matchedPluginIds.length === 0) {
|
||||
return undefined;
|
||||
}
|
||||
const uniquePluginIds = dedupeSortedPluginIds(matchedPluginIds);
|
||||
if (uniquePluginIds.length <= 1) {
|
||||
return uniquePluginIds;
|
||||
}
|
||||
const nonBundledPluginIds = uniquePluginIds.filter((pluginId) => {
|
||||
const plugin = registry.plugins.find((entry) => entry.id === pluginId);
|
||||
return plugin?.origin !== "bundled";
|
||||
});
|
||||
if (nonBundledPluginIds.length === 1) {
|
||||
return nonBundledPluginIds;
|
||||
}
|
||||
if (nonBundledPluginIds.length > 1) {
|
||||
return undefined;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
export function resolveOwningPluginIdsForProvider(params: {
|
||||
provider: string;
|
||||
config?: PluginLoadOptions["config"];
|
||||
workspaceDir?: string;
|
||||
env?: PluginLoadOptions["env"];
|
||||
manifestRegistry?: PluginManifestRegistry;
|
||||
}): string[] | undefined {
|
||||
const normalizedProvider = normalizeProviderId(params.provider);
|
||||
if (!normalizedProvider) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const registry = loadPluginManifestRegistry({
|
||||
config: params.config,
|
||||
workspaceDir: params.workspaceDir,
|
||||
env: params.env,
|
||||
});
|
||||
const registry = resolveManifestRegistry(params);
|
||||
const pluginIds = registry.plugins
|
||||
.filter((plugin) =>
|
||||
plugin.providers.some((providerId) => normalizeProviderId(providerId) === normalizedProvider),
|
||||
@@ -95,6 +189,65 @@ export function resolveOwningPluginIdsForProvider(params: {
|
||||
return pluginIds.length > 0 ? pluginIds : undefined;
|
||||
}
|
||||
|
||||
export function resolveOwningPluginIdsForModelRef(params: {
|
||||
model: string;
|
||||
config?: PluginLoadOptions["config"];
|
||||
workspaceDir?: string;
|
||||
env?: PluginLoadOptions["env"];
|
||||
manifestRegistry?: PluginManifestRegistry;
|
||||
}): string[] | undefined {
|
||||
const parsed = splitExplicitModelRef(params.model);
|
||||
if (!parsed) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
if (parsed.provider) {
|
||||
return resolveOwningPluginIdsForProvider({
|
||||
provider: parsed.provider,
|
||||
config: params.config,
|
||||
workspaceDir: params.workspaceDir,
|
||||
env: params.env,
|
||||
manifestRegistry: params.manifestRegistry,
|
||||
});
|
||||
}
|
||||
|
||||
const registry = resolveManifestRegistry(params);
|
||||
const matchedByPattern = registry.plugins
|
||||
.filter((plugin) => resolveModelSupportMatchKind(plugin, parsed.modelId) === "pattern")
|
||||
.map((plugin) => plugin.id);
|
||||
const preferredPatternPluginIds = resolvePreferredManifestPluginIds(registry, matchedByPattern);
|
||||
if (preferredPatternPluginIds) {
|
||||
return preferredPatternPluginIds;
|
||||
}
|
||||
|
||||
const matchedByPrefix = registry.plugins
|
||||
.filter((plugin) => resolveModelSupportMatchKind(plugin, parsed.modelId) === "prefix")
|
||||
.map((plugin) => plugin.id);
|
||||
return resolvePreferredManifestPluginIds(registry, matchedByPrefix);
|
||||
}
|
||||
|
||||
export function resolveOwningPluginIdsForModelRefs(params: {
|
||||
models: readonly string[];
|
||||
config?: PluginLoadOptions["config"];
|
||||
workspaceDir?: string;
|
||||
env?: PluginLoadOptions["env"];
|
||||
manifestRegistry?: PluginManifestRegistry;
|
||||
}): string[] {
|
||||
const registry = resolveManifestRegistry(params);
|
||||
return dedupeSortedPluginIds(
|
||||
params.models.flatMap(
|
||||
(model) =>
|
||||
resolveOwningPluginIdsForModelRef({
|
||||
model,
|
||||
config: params.config,
|
||||
workspaceDir: params.workspaceDir,
|
||||
env: params.env,
|
||||
manifestRegistry: registry,
|
||||
}) ?? [],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
export function resolveNonBundledProviderPluginIds(params: {
|
||||
config?: PluginLoadOptions["config"];
|
||||
workspaceDir?: string;
|
||||
|
||||
Reference in New Issue
Block a user