fix: avoid broad runtime catalog supplements

This commit is contained in:
Shakker
2026-04-27 19:10:06 +01:00
parent 8ac10cf164
commit 7231fcfec3
4 changed files with 50 additions and 25 deletions

View File

@@ -51,6 +51,10 @@ Notes:
rows from plugin manifests or bundled provider catalog metadata even when you
have not authenticated with that provider yet. Those rows still show as
unavailable until matching auth is configured.
- Broad `models list --all` merges manifest catalog rows over registry rows
without loading provider runtime supplement hooks. Provider-filtered manifest
fast paths only use providers marked `static`, so partial `refreshable`
manifest rows do not hide registry-backed provider lists.
- `models list` keeps native model metadata and runtime caps distinct. In table
output, `Ctx` shows `contextTokens/contextWindow` when an effective runtime
cap differs from the native context window; JSON rows include `contextTokens`

View File

@@ -49,7 +49,7 @@ const mocks = vi.hoisted(() => {
loadModelRegistry: vi.fn(),
loadModelCatalog: vi.fn(),
loadProviderCatalogModelsForList: vi.fn(),
loadStaticManifestCatalogRowsForList: vi.fn(),
loadManifestCatalogRowsForList: vi.fn(),
loadProviderIndexCatalogRowsForList: vi.fn(),
hasProviderStaticCatalogForFilter: vi.fn(),
resolveConfiguredEntries: vi.fn(),
@@ -78,7 +78,7 @@ function resetMocks() {
});
mocks.loadModelCatalog.mockResolvedValue([]);
mocks.loadProviderCatalogModelsForList.mockResolvedValue([]);
mocks.loadStaticManifestCatalogRowsForList.mockReturnValue([]);
mocks.loadManifestCatalogRowsForList.mockReturnValue([]);
mocks.loadProviderIndexCatalogRowsForList.mockReturnValue([]);
mocks.hasProviderStaticCatalogForFilter.mockResolvedValue(false);
mocks.resolveConfiguredEntries.mockReturnValue({
@@ -143,7 +143,7 @@ function installModelsListCommandForwardCompatMocks() {
}));
vi.doMock("./list.manifest-catalog.js", () => ({
loadStaticManifestCatalogRowsForList: mocks.loadStaticManifestCatalogRowsForList,
loadManifestCatalogRowsForList: mocks.loadManifestCatalogRowsForList,
}));
vi.doMock("./list.provider-index-catalog.js", () => ({
@@ -525,7 +525,7 @@ describe("modelsListCommand forward-compat", () => {
it("uses manifest catalog rows before provider runtime catalog rows", async () => {
mocks.resolveConfiguredEntries.mockReturnValueOnce({ entries: [] });
mocks.loadStaticManifestCatalogRowsForList.mockReturnValueOnce([
mocks.loadManifestCatalogRowsForList.mockReturnValueOnce([
{
provider: "moonshot",
id: "kimi-k2.6",
@@ -596,7 +596,7 @@ describe("modelsListCommand forward-compat", () => {
getAll: () => [{ ...OPENAI_CODEX_MODEL }],
},
});
mocks.loadStaticManifestCatalogRowsForList.mockReturnValueOnce([
mocks.loadManifestCatalogRowsForList.mockReturnValueOnce([
{
provider: "moonshot",
id: "kimi-k2.6",
@@ -622,6 +622,7 @@ describe("modelsListCommand forward-compat", () => {
});
expect(mocks.loadProviderCatalogModelsForList).not.toHaveBeenCalled();
expect(mocks.resolveModelWithRegistry).not.toHaveBeenCalled();
expect(mocks.loadModelCatalog).not.toHaveBeenCalled();
expect(lastPrintedRows<{ key: string }>()).toEqual([
expect.objectContaining({
key: "openai-codex/gpt-5.4",

View File

@@ -8,7 +8,10 @@ import {
resolveAwsSdkEnvVarName,
resolveEnvApiKey,
} from "../../agents/model-auth.js";
import { shouldSuppressBuiltInModel } from "../../agents/model-suppression.js";
import {
shouldSuppressBuiltInModel,
shouldSuppressBuiltInModelFromManifest,
} from "../../agents/model-suppression.js";
import { discoverAuthStorage, discoverModels } from "../../agents/pi-model-discovery.js";
import type { OpenClawConfig } from "../../config/types.openclaw.js";
import { resolveRuntimeSyntheticAuthProviderRefs } from "../../plugins/synthetic-auth.runtime.js";
@@ -85,7 +88,11 @@ function validateAvailableModels(availableModels: unknown): Model<Api>[] {
return availableModels as Model<Api>[];
}
function loadAvailableModels(registry: ModelRegistry, cfg: OpenClawConfig): Model<Api>[] {
function loadAvailableModels(
registry: ModelRegistry,
cfg: OpenClawConfig,
opts?: { runtimeSuppression?: boolean },
): Model<Api>[] {
let availableModels: unknown;
try {
availableModels = registry.getAvailable();
@@ -93,14 +100,19 @@ function loadAvailableModels(registry: ModelRegistry, cfg: OpenClawConfig): Mode
throw normalizeAvailabilityError(err);
}
try {
return validateAvailableModels(availableModels).filter(
(model) =>
!shouldSuppressBuiltInModel({
provider: model.provider,
id: model.id,
baseUrl: model.baseUrl,
config: cfg,
}),
return validateAvailableModels(availableModels).filter((model) =>
opts?.runtimeSuppression === false
? !shouldSuppressBuiltInModelFromManifest({
provider: model.provider,
id: model.id,
config: cfg,
})
: !shouldSuppressBuiltInModel({
provider: model.provider,
id: model.id,
baseUrl: model.baseUrl,
config: cfg,
}),
);
} catch (err) {
throw normalizeAvailabilityError(err);
@@ -111,26 +123,32 @@ export async function loadModelRegistry(
cfg: OpenClawConfig,
opts?: { providerFilter?: string; normalizeModels?: boolean },
) {
const runtimeSuppression = opts?.normalizeModels !== false;
const agentDir = resolveOpenClawAgentDir();
const authStorage = discoverAuthStorage(agentDir, { readOnly: true });
const registry = discoverModels(authStorage, agentDir, {
providerFilter: opts?.providerFilter,
normalizeModels: opts?.normalizeModels,
});
const models = registry.getAll().filter(
(model) =>
!shouldSuppressBuiltInModel({
provider: model.provider,
id: model.id,
baseUrl: model.baseUrl,
config: cfg,
}),
const models = registry.getAll().filter((model) =>
runtimeSuppression
? !shouldSuppressBuiltInModel({
provider: model.provider,
id: model.id,
baseUrl: model.baseUrl,
config: cfg,
})
: !shouldSuppressBuiltInModelFromManifest({
provider: model.provider,
id: model.id,
config: cfg,
}),
);
let availableKeys: Set<string> | undefined;
let availabilityErrorMessage: string | undefined;
try {
const availableModels = loadAvailableModels(registry, cfg);
const availableModels = loadAvailableModels(registry, cfg, { runtimeSuppression });
availableKeys = new Set(availableModels.map((model) => modelKey(model.provider, model.id)));
} catch (err) {
if (!shouldFallbackToAuthHeuristics(err)) {

View File

@@ -109,13 +109,15 @@ export async function appendAllModelRowSources(
});
}
if (params.modelRegistry) {
if (params.modelRegistry && params.context.filter.provider) {
await appendCatalogSupplementRows({
rows: params.rows,
modelRegistry: params.modelRegistry,
context: params.context,
seenKeys,
});
}
if (params.modelRegistry) {
return { requiresRegistryFallback: false };
}