mirror of
https://github.com/openclaw/openclaw.git
synced 2026-06-24 09:29:35 +00:00
fix provider static model fallback (#92293)
This commit is contained in:
@@ -5,6 +5,13 @@ const manifestMocks = vi.hoisted(() => ({
|
||||
listOpenClawPluginManifestMetadata: vi.fn(),
|
||||
loadPluginManifest: vi.fn(),
|
||||
}));
|
||||
const providerMocks = vi.hoisted(() => ({
|
||||
normalizePluginDiscoveryResult: vi.fn(),
|
||||
resolveBundledProviderCompatPluginIds: vi.fn(),
|
||||
resolveOwningPluginIdsForProviderRef: vi.fn(),
|
||||
resolveRuntimePluginDiscoveryProviders: vi.fn(),
|
||||
runProviderStaticCatalog: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock("../../plugins/manifest-metadata-scan.js", () => ({
|
||||
listOpenClawPluginManifestMetadata: manifestMocks.listOpenClawPluginManifestMetadata,
|
||||
@@ -15,7 +22,24 @@ vi.mock("../../plugins/manifest.js", async (importOriginal) => ({
|
||||
loadPluginManifest: manifestMocks.loadPluginManifest,
|
||||
}));
|
||||
|
||||
import { resolveBundledStaticCatalogModel } from "./model.static-catalog.js";
|
||||
vi.mock("../../plugins/providers.js", async (importOriginal) => ({
|
||||
...(await importOriginal<typeof import("../../plugins/providers.js")>()),
|
||||
resolveBundledProviderCompatPluginIds: providerMocks.resolveBundledProviderCompatPluginIds,
|
||||
resolveOwningPluginIdsForProviderRef: providerMocks.resolveOwningPluginIdsForProviderRef,
|
||||
}));
|
||||
|
||||
vi.mock("../../plugins/provider-discovery.js", async (importOriginal) => ({
|
||||
...(await importOriginal<typeof import("../../plugins/provider-discovery.js")>()),
|
||||
normalizePluginDiscoveryResult: providerMocks.normalizePluginDiscoveryResult,
|
||||
resolveRuntimePluginDiscoveryProviders: providerMocks.resolveRuntimePluginDiscoveryProviders,
|
||||
runProviderStaticCatalog: providerMocks.runProviderStaticCatalog,
|
||||
}));
|
||||
|
||||
import { getModelProviderRequestTransport } from "../provider-request-config.js";
|
||||
import {
|
||||
resolveBundledProviderStaticCatalogModel,
|
||||
resolveBundledStaticCatalogModel,
|
||||
} from "./model.static-catalog.js";
|
||||
|
||||
function setManifestPlugins(plugins: unknown[]) {
|
||||
// Static catalog resolution reads scan metadata first, then loads the manifest
|
||||
@@ -81,7 +105,17 @@ function createMistralManifestPlugin(overrides?: {
|
||||
beforeEach(() => {
|
||||
manifestMocks.listOpenClawPluginManifestMetadata.mockReset();
|
||||
manifestMocks.loadPluginManifest.mockReset();
|
||||
providerMocks.normalizePluginDiscoveryResult.mockReset();
|
||||
providerMocks.resolveBundledProviderCompatPluginIds.mockReset();
|
||||
providerMocks.resolveOwningPluginIdsForProviderRef.mockReset();
|
||||
providerMocks.resolveRuntimePluginDiscoveryProviders.mockReset();
|
||||
providerMocks.runProviderStaticCatalog.mockReset();
|
||||
setManifestPlugins([]);
|
||||
providerMocks.resolveBundledProviderCompatPluginIds.mockReturnValue([]);
|
||||
providerMocks.resolveOwningPluginIdsForProviderRef.mockReturnValue(undefined);
|
||||
providerMocks.resolveRuntimePluginDiscoveryProviders.mockResolvedValue([]);
|
||||
providerMocks.runProviderStaticCatalog.mockResolvedValue(undefined);
|
||||
providerMocks.normalizePluginDiscoveryResult.mockReturnValue({});
|
||||
});
|
||||
|
||||
describe("resolveBundledStaticCatalogModel", () => {
|
||||
@@ -166,3 +200,132 @@ describe("resolveBundledStaticCatalogModel", () => {
|
||||
).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe("resolveBundledProviderStaticCatalogModel", () => {
|
||||
it("resolves exact rows from bundled provider static catalogs", async () => {
|
||||
const cfg = { plugins: { entries: { google: { enabled: true } } } };
|
||||
const provider = {
|
||||
id: "google",
|
||||
pluginId: "google",
|
||||
label: "Google",
|
||||
auth: [],
|
||||
staticCatalog: { run: vi.fn() },
|
||||
};
|
||||
providerMocks.resolveOwningPluginIdsForProviderRef.mockReturnValue(["google"]);
|
||||
providerMocks.resolveBundledProviderCompatPluginIds.mockReturnValue(["google"]);
|
||||
providerMocks.resolveRuntimePluginDiscoveryProviders.mockResolvedValue([provider]);
|
||||
providerMocks.runProviderStaticCatalog.mockResolvedValue({ marker: "static-result" });
|
||||
providerMocks.normalizePluginDiscoveryResult.mockReturnValue({
|
||||
google: {
|
||||
api: "google-generative-ai",
|
||||
authHeader: true,
|
||||
baseUrl: "https://generativelanguage.googleapis.com/v1beta",
|
||||
request: { headers: { "X-Static-Catalog": "yes" } },
|
||||
models: [
|
||||
{
|
||||
id: "gemini-3.1-pro-preview",
|
||||
name: "Gemini 3.1 Pro Preview",
|
||||
reasoning: true,
|
||||
input: ["text", "image"],
|
||||
cost: { input: 2, output: 12, cacheRead: 0.5, cacheWrite: 0 },
|
||||
contextWindow: 1_048_576,
|
||||
maxTokens: 65_536,
|
||||
mediaInput: { image: { maxSidePx: 3072, tokenMode: "provider" } },
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
|
||||
const model = await resolveBundledProviderStaticCatalogModel({
|
||||
provider: "google",
|
||||
modelId: "gemini-3.1-pro-preview",
|
||||
cfg,
|
||||
});
|
||||
|
||||
expect(model).toMatchObject({
|
||||
api: "google-generative-ai",
|
||||
authHeader: true,
|
||||
baseUrl: "https://generativelanguage.googleapis.com/v1beta",
|
||||
contextTokens: undefined,
|
||||
contextWindow: 1_048_576,
|
||||
cost: { input: 2, output: 12, cacheRead: 0.5, cacheWrite: 0 },
|
||||
headers: { "X-Static-Catalog": "yes" },
|
||||
id: "gemini-3.1-pro-preview",
|
||||
input: ["text", "image"],
|
||||
maxTokens: 65_536,
|
||||
mediaInput: { image: { maxSidePx: 3072, tokenMode: "provider" } },
|
||||
name: "Gemini 3.1 Pro Preview",
|
||||
provider: "google",
|
||||
reasoning: true,
|
||||
});
|
||||
expect(getModelProviderRequestTransport(model!)).toEqual({
|
||||
headers: { "X-Static-Catalog": "yes" },
|
||||
});
|
||||
expect(providerMocks.resolveRuntimePluginDiscoveryProviders).toHaveBeenCalledWith({
|
||||
config: cfg,
|
||||
workspaceDir: undefined,
|
||||
env: process.env,
|
||||
onlyPluginIds: ["google"],
|
||||
includeUntrustedWorkspacePlugins: false,
|
||||
requireCompleteDiscoveryEntryCoverage: true,
|
||||
discoveryEntriesOnly: true,
|
||||
includeManifestModelCatalogProviders: false,
|
||||
});
|
||||
expect(providerMocks.runProviderStaticCatalog).toHaveBeenCalledWith({
|
||||
provider,
|
||||
config: cfg,
|
||||
workspaceDir: undefined,
|
||||
env: process.env,
|
||||
});
|
||||
});
|
||||
|
||||
it("does not load provider catalogs when the provider owner is not bundled and enabled", async () => {
|
||||
providerMocks.resolveOwningPluginIdsForProviderRef.mockReturnValue(["google"]);
|
||||
providerMocks.resolveBundledProviderCompatPluginIds.mockReturnValue([]);
|
||||
|
||||
await expect(
|
||||
resolveBundledProviderStaticCatalogModel({
|
||||
provider: "google",
|
||||
modelId: "gemini-3.1-pro-preview",
|
||||
cfg: {},
|
||||
}),
|
||||
).resolves.toBeUndefined();
|
||||
|
||||
expect(providerMocks.resolveRuntimePluginDiscoveryProviders).not.toHaveBeenCalled();
|
||||
expect(providerMocks.runProviderStaticCatalog).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("requires an exact provider and model match", async () => {
|
||||
const provider = { id: "google", pluginId: "google", label: "Google", auth: [] };
|
||||
providerMocks.resolveOwningPluginIdsForProviderRef.mockReturnValue(["google"]);
|
||||
providerMocks.resolveBundledProviderCompatPluginIds.mockReturnValue(["google"]);
|
||||
providerMocks.resolveRuntimePluginDiscoveryProviders.mockResolvedValue([provider]);
|
||||
providerMocks.normalizePluginDiscoveryResult.mockReturnValue({
|
||||
google: {
|
||||
api: "google-generative-ai",
|
||||
baseUrl: "https://generativelanguage.googleapis.com/v1beta",
|
||||
models: [{ id: "gemini-3.1-pro-preview", name: "Gemini 3.1 Pro Preview" }],
|
||||
},
|
||||
"google-vertex": {
|
||||
api: "google-vertex",
|
||||
baseUrl: "https://aiplatform.googleapis.com/v1",
|
||||
models: [{ id: "gemini-3.1-pro-preview", name: "Gemini 3.1 Pro Preview" }],
|
||||
},
|
||||
});
|
||||
|
||||
await expect(
|
||||
resolveBundledProviderStaticCatalogModel({
|
||||
provider: "google",
|
||||
modelId: "gemini-2.5-pro",
|
||||
cfg: {},
|
||||
}),
|
||||
).resolves.toBeUndefined();
|
||||
await expect(
|
||||
resolveBundledProviderStaticCatalogModel({
|
||||
provider: "openrouter",
|
||||
modelId: "gemini-3.1-pro-preview",
|
||||
cfg: {},
|
||||
}),
|
||||
).resolves.toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -3,15 +3,26 @@
|
||||
*/
|
||||
import type { NormalizedModelCatalogRow } from "@openclaw/model-catalog-core/model-catalog-types";
|
||||
import { normalizeProviderId } from "@openclaw/model-catalog-core/provider-id";
|
||||
import type { ModelProviderConfig } from "../../config/types.models.js";
|
||||
import type { OpenClawConfig } from "../../config/types.openclaw.js";
|
||||
import { planManifestModelCatalogRows } from "../../model-catalog/manifest-planner.js";
|
||||
import { listOpenClawPluginManifestMetadata } from "../../plugins/manifest-metadata-scan.js";
|
||||
import { loadPluginManifestRegistry } from "../../plugins/manifest-registry.js";
|
||||
import type { PluginManifestRecord } from "../../plugins/manifest-registry.js";
|
||||
import { loadPluginManifest } from "../../plugins/manifest.js";
|
||||
import {
|
||||
normalizePluginDiscoveryResult,
|
||||
resolveRuntimePluginDiscoveryProviders,
|
||||
runProviderStaticCatalog,
|
||||
} from "../../plugins/provider-discovery.js";
|
||||
import type { ProviderRuntimeModel } from "../../plugins/provider-runtime-model.types.js";
|
||||
import {
|
||||
resolveBundledProviderCompatPluginIds,
|
||||
resolveOwningPluginIdsForProviderRef,
|
||||
} from "../../plugins/providers.js";
|
||||
import { DEFAULT_CONTEXT_TOKENS } from "../defaults.js";
|
||||
import { normalizeStaticProviderModelId } from "../model-ref-shared.js";
|
||||
import { buildInlineProviderModels } from "./model.inline-provider.js";
|
||||
|
||||
/**
|
||||
* Resolves bundled plugin static model-catalog rows into runtime model records.
|
||||
@@ -20,21 +31,35 @@ function rowMatchesModel(params: {
|
||||
row: NormalizedModelCatalogRow;
|
||||
provider: string;
|
||||
modelId: string;
|
||||
}): boolean {
|
||||
return staticModelIdMatches({
|
||||
candidateId: params.row.id,
|
||||
provider: params.provider,
|
||||
modelId: params.modelId,
|
||||
rowProvider: params.row.provider,
|
||||
});
|
||||
}
|
||||
|
||||
function staticModelIdMatches(params: {
|
||||
candidateId: string;
|
||||
provider: string;
|
||||
modelId: string;
|
||||
rowProvider?: string;
|
||||
}): boolean {
|
||||
const normalizedProvider = normalizeProviderId(params.provider);
|
||||
if (normalizeProviderId(params.row.provider) !== normalizedProvider) {
|
||||
if (params.rowProvider && normalizeProviderId(params.rowProvider) !== normalizedProvider) {
|
||||
return false;
|
||||
}
|
||||
return (
|
||||
normalizeStaticProviderModelId(normalizedProvider, params.row.id).trim().toLowerCase() ===
|
||||
normalizeStaticProviderModelId(normalizedProvider, params.candidateId).trim().toLowerCase() ===
|
||||
normalizeStaticProviderModelId(normalizedProvider, params.modelId).trim().toLowerCase()
|
||||
);
|
||||
}
|
||||
|
||||
function normalizeStaticCatalogInput(
|
||||
input: NormalizedModelCatalogRow["input"],
|
||||
input: readonly unknown[] | undefined,
|
||||
): ProviderRuntimeModel["input"] {
|
||||
const normalizedInput = input.filter(
|
||||
const normalizedInput = (input ?? []).filter(
|
||||
(item): item is "text" | "image" => item === "text" || item === "image",
|
||||
);
|
||||
return normalizedInput.length > 0 ? normalizedInput : ["text"];
|
||||
@@ -71,6 +96,42 @@ function modelFromStaticCatalogRow(row: NormalizedModelCatalogRow): ProviderRunt
|
||||
};
|
||||
}
|
||||
|
||||
function modelFromProviderStaticCatalog(params: {
|
||||
provider: string;
|
||||
providerConfig: ModelProviderConfig;
|
||||
model: ModelProviderConfig["models"][number];
|
||||
}): ProviderRuntimeModel {
|
||||
const [model] = buildInlineProviderModels({
|
||||
[params.provider]: { ...params.providerConfig, models: [params.model] },
|
||||
});
|
||||
return {
|
||||
...model,
|
||||
id: model?.id ?? params.model.id,
|
||||
name: model?.name || params.model.name || params.model.id,
|
||||
provider: params.provider,
|
||||
api: model?.api ?? params.model.api ?? params.providerConfig.api ?? "openai-responses",
|
||||
baseUrl: model?.baseUrl ?? params.model.baseUrl ?? params.providerConfig.baseUrl ?? "",
|
||||
reasoning: model?.reasoning ?? params.model.reasoning ?? false,
|
||||
input: normalizeStaticCatalogInput(model?.input ?? params.model.input),
|
||||
cost: model?.cost ?? normalizeStaticCatalogCost(params.model.cost),
|
||||
contextWindow:
|
||||
model?.contextWindow ??
|
||||
params.model.contextWindow ??
|
||||
params.providerConfig.contextWindow ??
|
||||
DEFAULT_CONTEXT_TOKENS,
|
||||
contextTokens:
|
||||
model?.contextTokens ?? params.model.contextTokens ?? params.providerConfig.contextTokens,
|
||||
maxTokens:
|
||||
model?.maxTokens ??
|
||||
params.model.maxTokens ??
|
||||
params.providerConfig.maxTokens ??
|
||||
DEFAULT_CONTEXT_TOKENS,
|
||||
...(params.providerConfig.authHeader !== undefined
|
||||
? { authHeader: params.providerConfig.authHeader }
|
||||
: {}),
|
||||
};
|
||||
}
|
||||
|
||||
type StaticCatalogPlugin = Parameters<
|
||||
typeof planManifestModelCatalogRows
|
||||
>[0]["registry"]["plugins"][number];
|
||||
@@ -210,3 +271,86 @@ export function resolveBundledStaticCatalogModel(params: {
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves one bundled provider static-catalog model row for provider/model lookup.
|
||||
*
|
||||
* Some bundled providers expose their canonical offline rows through
|
||||
* `providerCatalogEntry` instead of manifest `modelCatalog`. This keeps the
|
||||
* skip-discovery fallback aligned with model list/inspect without running live
|
||||
* discovery or untrusted workspace plugins.
|
||||
*/
|
||||
export async function resolveBundledProviderStaticCatalogModel(params: {
|
||||
provider: string;
|
||||
modelId: string;
|
||||
cfg?: OpenClawConfig;
|
||||
workspaceDir?: string;
|
||||
env?: NodeJS.ProcessEnv;
|
||||
}): Promise<ProviderRuntimeModel | undefined> {
|
||||
const env = params.env ?? process.env;
|
||||
const provider = normalizeProviderId(params.provider);
|
||||
if (!provider || !params.modelId.trim()) {
|
||||
return undefined;
|
||||
}
|
||||
const pluginIds = resolveOwningPluginIdsForProviderRef({
|
||||
provider,
|
||||
config: params.cfg,
|
||||
workspaceDir: params.workspaceDir,
|
||||
env,
|
||||
});
|
||||
if (!pluginIds || pluginIds.length === 0) {
|
||||
return undefined;
|
||||
}
|
||||
const bundledPluginIds = new Set(
|
||||
resolveBundledProviderCompatPluginIds({
|
||||
config: params.cfg,
|
||||
workspaceDir: params.workspaceDir,
|
||||
env,
|
||||
}),
|
||||
);
|
||||
const scopedPluginIds = pluginIds.filter((pluginId) => bundledPluginIds.has(pluginId));
|
||||
if (scopedPluginIds.length === 0) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const providers = await resolveRuntimePluginDiscoveryProviders({
|
||||
config: params.cfg,
|
||||
workspaceDir: params.workspaceDir,
|
||||
env,
|
||||
onlyPluginIds: scopedPluginIds,
|
||||
includeUntrustedWorkspacePlugins: false,
|
||||
requireCompleteDiscoveryEntryCoverage: true,
|
||||
discoveryEntriesOnly: true,
|
||||
includeManifestModelCatalogProviders: false,
|
||||
});
|
||||
|
||||
for (const catalogProvider of providers) {
|
||||
const result = await runProviderStaticCatalog({
|
||||
provider: catalogProvider,
|
||||
config: params.cfg ?? {},
|
||||
workspaceDir: params.workspaceDir,
|
||||
env,
|
||||
});
|
||||
const normalized = normalizePluginDiscoveryResult({
|
||||
provider: catalogProvider,
|
||||
result,
|
||||
});
|
||||
for (const [providerIdRaw, providerConfig] of Object.entries(normalized)) {
|
||||
const providerId = normalizeProviderId(providerIdRaw);
|
||||
if (providerId !== provider || !Array.isArray(providerConfig.models)) {
|
||||
continue;
|
||||
}
|
||||
const model = providerConfig.models.find((candidate) =>
|
||||
staticModelIdMatches({
|
||||
candidateId: candidate.id,
|
||||
provider,
|
||||
modelId: params.modelId,
|
||||
}),
|
||||
);
|
||||
if (model) {
|
||||
return modelFromProviderStaticCatalog({ provider, providerConfig, model });
|
||||
}
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@ import { resetModelDiscoveryCacheForTest } from "./model-discovery-cache.js";
|
||||
import { createProviderRuntimeTestMock } from "./model.provider-runtime.test-support.js";
|
||||
|
||||
const resolveBundledStaticCatalogModelMock = vi.hoisted(() => vi.fn());
|
||||
const resolveBundledProviderStaticCatalogModelMock = vi.hoisted(() => vi.fn());
|
||||
const resolveRuntimeSyntheticAuthProviderRefsMock = vi.hoisted(() => vi.fn((): string[] => []));
|
||||
const resolveRuntimeExternalAuthProviderRefsMock = vi.hoisted(() => vi.fn((): string[] => []));
|
||||
|
||||
@@ -134,6 +135,7 @@ vi.mock("./model.static-catalog.js", async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import("./model.static-catalog.js")>();
|
||||
return {
|
||||
...actual,
|
||||
resolveBundledProviderStaticCatalogModel: resolveBundledProviderStaticCatalogModelMock,
|
||||
resolveBundledStaticCatalogModel: resolveBundledStaticCatalogModelMock,
|
||||
};
|
||||
});
|
||||
@@ -187,6 +189,7 @@ beforeEach(() => {
|
||||
mockLoadOpenRouterModelCapabilities.mockReset();
|
||||
mockLoadOpenRouterModelCapabilities.mockResolvedValue();
|
||||
resolveBundledStaticCatalogModelMock.mockReset();
|
||||
resolveBundledProviderStaticCatalogModelMock.mockReset();
|
||||
});
|
||||
|
||||
function createRuntimeHooks() {
|
||||
@@ -568,6 +571,58 @@ describe("resolveModel", () => {
|
||||
cfg: undefined,
|
||||
workspaceDir: undefined,
|
||||
});
|
||||
expect(resolveBundledProviderStaticCatalogModelMock).not.toHaveBeenCalled();
|
||||
expect(discoverAuthStorage).not.toHaveBeenCalled();
|
||||
expect(discoverModels).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("resolves opt-in provider static catalog rows while skipping agent discovery", async () => {
|
||||
resolveBundledProviderStaticCatalogModelMock.mockResolvedValueOnce({
|
||||
provider: "google",
|
||||
id: "gemini-3.1-pro-preview",
|
||||
name: "Gemini 3.1 Pro Preview",
|
||||
api: "google-generative-ai",
|
||||
baseUrl: "https://generativelanguage.googleapis.com/v1beta",
|
||||
reasoning: true,
|
||||
input: ["text", "image"],
|
||||
cost: { input: 2, output: 12, cacheRead: 0.5, cacheWrite: 0 },
|
||||
contextWindow: 1_048_576,
|
||||
maxTokens: 65_536,
|
||||
});
|
||||
|
||||
const result = await resolveModelAsync(
|
||||
"google",
|
||||
"gemini-3.1-pro-preview",
|
||||
"/tmp/agent",
|
||||
undefined,
|
||||
{
|
||||
allowBundledStaticCatalogFallback: true,
|
||||
runtimeHooks: createRuntimeHooks(),
|
||||
skipAgentDiscovery: true,
|
||||
},
|
||||
);
|
||||
|
||||
expectRecordFields(expectResolvedModel(result), {
|
||||
provider: "google",
|
||||
id: "gemini-3.1-pro-preview",
|
||||
api: "google-generative-ai",
|
||||
baseUrl: "https://generativelanguage.googleapis.com/v1beta",
|
||||
reasoning: true,
|
||||
contextWindow: 1_048_576,
|
||||
maxTokens: 65_536,
|
||||
});
|
||||
expect(resolveBundledStaticCatalogModelMock).toHaveBeenCalledWith({
|
||||
provider: "google",
|
||||
modelId: "gemini-3.1-pro-preview",
|
||||
cfg: undefined,
|
||||
workspaceDir: undefined,
|
||||
});
|
||||
expect(resolveBundledProviderStaticCatalogModelMock).toHaveBeenCalledWith({
|
||||
provider: "google",
|
||||
modelId: "gemini-3.1-pro-preview",
|
||||
cfg: undefined,
|
||||
workspaceDir: undefined,
|
||||
});
|
||||
expect(discoverAuthStorage).not.toHaveBeenCalled();
|
||||
expect(discoverModels).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
@@ -54,6 +54,7 @@ import {
|
||||
import { normalizeResolvedProviderModel } from "./model.provider-normalization.js";
|
||||
import {
|
||||
canonicalizeManifestModelCatalogProviderAlias,
|
||||
resolveBundledProviderStaticCatalogModel,
|
||||
resolveBundledStaticCatalogModel,
|
||||
} from "./model.static-catalog.js";
|
||||
|
||||
@@ -1566,25 +1567,32 @@ export async function resolveModelAsync(
|
||||
authProfileId: options?.authProfileId,
|
||||
preferredProfile: options?.preferredProfile,
|
||||
});
|
||||
let staticCatalogLookupComplete = false;
|
||||
let staticCatalogModel: ReturnType<typeof resolveBundledStaticCatalogModel> | undefined;
|
||||
const resolveStaticCatalogModel = () => {
|
||||
let staticCatalogLookup: Promise<ProviderRuntimeModel | undefined> | undefined;
|
||||
const resolveStaticCatalogModel = async () => {
|
||||
if (!options?.allowBundledStaticCatalogFallback) {
|
||||
return undefined;
|
||||
}
|
||||
if (!staticCatalogLookupComplete) {
|
||||
staticCatalogLookupComplete = true;
|
||||
staticCatalogModel = resolveBundledStaticCatalogModel({
|
||||
staticCatalogLookup ??= (async () => {
|
||||
const manifestModel = resolveBundledStaticCatalogModel({
|
||||
provider: normalizedRef.provider,
|
||||
modelId: normalizedRef.model,
|
||||
cfg,
|
||||
workspaceDir,
|
||||
});
|
||||
}
|
||||
return staticCatalogModel;
|
||||
if (manifestModel) {
|
||||
return manifestModel;
|
||||
}
|
||||
return await resolveBundledProviderStaticCatalogModel({
|
||||
provider: normalizedRef.provider,
|
||||
modelId: normalizedRef.model,
|
||||
cfg,
|
||||
workspaceDir,
|
||||
});
|
||||
})();
|
||||
return await staticCatalogLookup;
|
||||
};
|
||||
const resolveStaticCatalogFallbackModel = () => {
|
||||
const catalogModel = resolveStaticCatalogModel();
|
||||
const resolveStaticCatalogFallbackModel = async () => {
|
||||
const catalogModel = await resolveStaticCatalogModel();
|
||||
if (!catalogModel) {
|
||||
return undefined;
|
||||
}
|
||||
@@ -1657,7 +1665,7 @@ export async function resolveModelAsync(
|
||||
model = await resolveDynamicAttempt();
|
||||
}
|
||||
if (!model && !explicitModel && options?.allowBundledStaticCatalogFallback) {
|
||||
model = resolveStaticCatalogFallbackModel();
|
||||
model = await resolveStaticCatalogFallbackModel();
|
||||
}
|
||||
if (!model && !explicitModel && options?.allowBundledStaticCatalogFallback) {
|
||||
model = resolveConfiguredFallbackModel({
|
||||
@@ -1670,7 +1678,7 @@ export async function resolveModelAsync(
|
||||
});
|
||||
}
|
||||
if (model && options?.allowBundledStaticCatalogFallback) {
|
||||
const staticMediaInput = resolveStaticCatalogModel()?.mediaInput;
|
||||
const staticMediaInput = (await resolveStaticCatalogModel())?.mediaInput;
|
||||
const resolvedMediaInput = (model as ProviderRuntimeModel).mediaInput;
|
||||
const mediaInput = mergeModelMediaInput(staticMediaInput, resolvedMediaInput);
|
||||
if (mediaInput) {
|
||||
|
||||
@@ -691,6 +691,25 @@ describe("resolvePluginDiscoveryProvidersRuntime", () => {
|
||||
expect(mocks.resolvePluginProviders).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("can omit manifest model catalogs from static discovery entries", () => {
|
||||
mocks.resolveDiscoveredProviderPluginIds.mockReturnValue(["openai"]);
|
||||
mocks.loadPluginMetadataSnapshot.mockReturnValue({
|
||||
index: { plugins: [] },
|
||||
manifestRegistry: {
|
||||
plugins: [createManifestPluginWithModelCatalog("openai")],
|
||||
diagnostics: [],
|
||||
},
|
||||
});
|
||||
|
||||
const providers = resolvePluginDiscoveryProvidersRuntime({
|
||||
discoveryEntriesOnly: true,
|
||||
includeManifestModelCatalogProviders: false,
|
||||
});
|
||||
|
||||
expect(providers).toStrictEqual([]);
|
||||
expect(mocks.resolvePluginProviders).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("defaults missing manifest model costs for static discovery entries", async () => {
|
||||
mocks.resolveDiscoveredProviderPluginIds.mockReturnValue(["anthropic"]);
|
||||
mocks.loadPluginMetadataSnapshot.mockReturnValue({
|
||||
|
||||
@@ -274,6 +274,7 @@ function resolveProviderDiscoveryEntryPlugins(params: {
|
||||
includeUntrustedWorkspacePlugins?: boolean;
|
||||
requireCompleteDiscoveryEntryCoverage?: boolean;
|
||||
discoveryEntriesOnly?: boolean;
|
||||
includeManifestModelCatalogProviders?: boolean;
|
||||
pluginMetadataSnapshot?: PluginMetadataRegistryView;
|
||||
}): ProviderDiscoveryEntryResult {
|
||||
const metadataSnapshot =
|
||||
@@ -295,7 +296,10 @@ function resolveProviderDiscoveryEntryPlugins(params: {
|
||||
const runtimeManifestCatalogPluginIds = resolveRuntimeManifestCatalogPluginIds(pluginRecords);
|
||||
const entryRecords = pluginRecords.filter((plugin) => plugin.providerDiscoverySource);
|
||||
const entryPluginIds = new Set(entryRecords.map((plugin) => plugin.id));
|
||||
const manifestProviders = resolveManifestModelCatalogProviders(pluginRecords);
|
||||
const manifestProviders =
|
||||
params.includeManifestModelCatalogProviders === false
|
||||
? []
|
||||
: resolveManifestModelCatalogProviders(pluginRecords);
|
||||
const manifestEntryPluginIds = new Set<string>();
|
||||
for (const pluginId of manifestProviders.map((provider) => provider.pluginId)) {
|
||||
if (pluginId) {
|
||||
@@ -427,6 +431,7 @@ export function resolvePluginDiscoveryProvidersRuntime(params: {
|
||||
includeUntrustedWorkspacePlugins?: boolean;
|
||||
requireCompleteDiscoveryEntryCoverage?: boolean;
|
||||
discoveryEntriesOnly?: boolean;
|
||||
includeManifestModelCatalogProviders?: boolean;
|
||||
pluginMetadataSnapshot?: PluginMetadataRegistryView;
|
||||
}): ProviderPlugin[] {
|
||||
const env = params.env ?? process.env;
|
||||
|
||||
@@ -46,6 +46,7 @@ export type ResolveRuntimePluginDiscoveryProvidersParams = {
|
||||
includeUntrustedWorkspacePlugins?: boolean;
|
||||
requireCompleteDiscoveryEntryCoverage?: boolean;
|
||||
discoveryEntriesOnly?: boolean;
|
||||
includeManifestModelCatalogProviders?: boolean;
|
||||
pluginMetadataSnapshot?: PluginMetadataRegistryView;
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user