mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 13:10:43 +00:00
fix: restore models list registry fallback
This commit is contained in:
@@ -468,6 +468,50 @@ describe("modelsListCommand forward-compat", () => {
|
||||
]);
|
||||
});
|
||||
|
||||
it("falls back to registry-backed rows when the fast-path catalog is empty", async () => {
|
||||
mocks.resolveConfiguredEntries.mockReturnValueOnce({ entries: [] });
|
||||
mocks.hasProviderStaticCatalogForFilter.mockResolvedValueOnce(true);
|
||||
mocks.loadProviderCatalogModelsForList.mockResolvedValueOnce([]).mockResolvedValueOnce([]);
|
||||
mocks.loadModelRegistry.mockResolvedValueOnce({
|
||||
models: [{ ...OPENAI_CODEX_MODEL }],
|
||||
availableKeys: new Set(["openai-codex/gpt-5.4"]),
|
||||
registry: {
|
||||
getAll: () => [{ ...OPENAI_CODEX_MODEL }],
|
||||
},
|
||||
});
|
||||
const runtime = createRuntime();
|
||||
|
||||
await modelsListCommand(
|
||||
{ all: true, provider: "openai-codex", json: true },
|
||||
runtime as never,
|
||||
);
|
||||
|
||||
expect(mocks.loadModelRegistry).toHaveBeenCalledWith(
|
||||
mocks.resolvedConfig,
|
||||
expect.objectContaining({
|
||||
providerFilter: "openai-codex",
|
||||
}),
|
||||
);
|
||||
expect(mocks.loadProviderCatalogModelsForList).toHaveBeenNthCalledWith(1, {
|
||||
cfg: mocks.resolvedConfig,
|
||||
agentDir: "/tmp/openclaw-agent",
|
||||
providerFilter: "openai-codex",
|
||||
staticOnly: true,
|
||||
});
|
||||
expect(mocks.loadProviderCatalogModelsForList).toHaveBeenNthCalledWith(2, {
|
||||
cfg: mocks.resolvedConfig,
|
||||
agentDir: "/tmp/openclaw-agent",
|
||||
providerFilter: "openai-codex",
|
||||
staticOnly: undefined,
|
||||
});
|
||||
expect(lastPrintedRows<{ key: string; available: boolean }>()).toEqual([
|
||||
expect.objectContaining({
|
||||
key: "openai-codex/gpt-5.4",
|
||||
available: true,
|
||||
}),
|
||||
]);
|
||||
});
|
||||
|
||||
it("keeps the registry path for provider filters without static catalog coverage", async () => {
|
||||
mocks.resolveConfiguredEntries.mockReturnValueOnce({ entries: [] });
|
||||
mocks.hasProviderStaticCatalogForFilter.mockResolvedValueOnce(false);
|
||||
|
||||
@@ -70,13 +70,16 @@ export async function modelsListCommand(
|
||||
providerFilter,
|
||||
useProviderCatalogFastPath,
|
||||
});
|
||||
const loadRegistryState = async () => {
|
||||
const loaded = await loadListModelRegistry(cfg, { providerFilter });
|
||||
modelRegistry = loaded.registry;
|
||||
discoveredKeys = loaded.discoveredKeys;
|
||||
availableKeys = loaded.availableKeys;
|
||||
availabilityErrorMessage = loaded.availabilityErrorMessage;
|
||||
};
|
||||
try {
|
||||
if (shouldLoadRegistry) {
|
||||
const loaded = await loadListModelRegistry(cfg, { providerFilter });
|
||||
modelRegistry = loaded.registry;
|
||||
discoveredKeys = loaded.discoveredKeys;
|
||||
availableKeys = loaded.availableKeys;
|
||||
availabilityErrorMessage = loaded.availabilityErrorMessage;
|
||||
await loadRegistryState();
|
||||
} else if (!opts.all) {
|
||||
const loaded = loadConfiguredListModelRegistry(cfg, entries, { providerFilter });
|
||||
modelRegistry = loaded.registry;
|
||||
@@ -88,14 +91,7 @@ export async function modelsListCommand(
|
||||
process.exitCode = 1;
|
||||
return;
|
||||
}
|
||||
if (availabilityErrorMessage !== undefined) {
|
||||
runtime.error(
|
||||
`Model availability lookup failed; falling back to auth heuristics for discovered models: ${availabilityErrorMessage}`,
|
||||
);
|
||||
}
|
||||
|
||||
const rows: ModelRow[] = [];
|
||||
const rowContext = {
|
||||
const buildRowContext = (skipRuntimeModelSuppression: boolean) => ({
|
||||
cfg,
|
||||
agentDir,
|
||||
authStore,
|
||||
@@ -106,16 +102,35 @@ export async function modelsListCommand(
|
||||
provider: providerFilter,
|
||||
local: opts.local,
|
||||
},
|
||||
skipRuntimeModelSuppression: useProviderCatalogFastPath,
|
||||
};
|
||||
skipRuntimeModelSuppression,
|
||||
});
|
||||
const rows: ModelRow[] = [];
|
||||
|
||||
if (opts.all) {
|
||||
await appendAllModelRowSources({
|
||||
let rowContext = buildRowContext(useProviderCatalogFastPath);
|
||||
const initialAppend = await appendAllModelRowSources({
|
||||
rows,
|
||||
context: rowContext,
|
||||
modelRegistry,
|
||||
useProviderCatalogFastPath,
|
||||
});
|
||||
if (initialAppend.requiresRegistryFallback) {
|
||||
try {
|
||||
await loadRegistryState();
|
||||
} catch (err) {
|
||||
runtime.error(`Model registry unavailable:\n${formatErrorWithStack(err)}`);
|
||||
process.exitCode = 1;
|
||||
return;
|
||||
}
|
||||
rows.length = 0;
|
||||
rowContext = buildRowContext(false);
|
||||
await appendAllModelRowSources({
|
||||
rows,
|
||||
context: rowContext,
|
||||
modelRegistry,
|
||||
useProviderCatalogFastPath: false,
|
||||
});
|
||||
}
|
||||
} else {
|
||||
const registry = modelRegistry;
|
||||
if (!registry) {
|
||||
@@ -127,10 +142,16 @@ export async function modelsListCommand(
|
||||
rows,
|
||||
entries,
|
||||
modelRegistry: registry,
|
||||
context: rowContext,
|
||||
context: buildRowContext(false),
|
||||
});
|
||||
}
|
||||
|
||||
if (availabilityErrorMessage !== undefined) {
|
||||
runtime.error(
|
||||
`Model availability lookup failed; falling back to auth heuristics for discovered models: ${availabilityErrorMessage}`,
|
||||
);
|
||||
}
|
||||
|
||||
if (rows.length === 0) {
|
||||
runtime.log("No models found.");
|
||||
return;
|
||||
|
||||
@@ -167,6 +167,30 @@ describe("loadProviderCatalogModelsForList", () => {
|
||||
);
|
||||
});
|
||||
|
||||
it("returns an empty catalog when a static provider catalog throws", async () => {
|
||||
providerDiscoveryMocks.resolvePluginDiscoveryProviders.mockResolvedValueOnce([
|
||||
{
|
||||
id: "moonshot",
|
||||
pluginId: "moonshot",
|
||||
label: "Moonshot",
|
||||
auth: [],
|
||||
staticCatalog: {
|
||||
run: async () => {
|
||||
throw new Error("catalog offline");
|
||||
},
|
||||
},
|
||||
},
|
||||
]);
|
||||
|
||||
await expect(
|
||||
loadProviderCatalogModelsForList({
|
||||
...baseParams,
|
||||
providerFilter: "moonshot",
|
||||
staticOnly: true,
|
||||
}),
|
||||
).resolves.toEqual([]);
|
||||
});
|
||||
|
||||
it("only skips registry for providers with actual static catalogs", async () => {
|
||||
providerDiscoveryMocks.resolvePluginDiscoveryProviders.mockResolvedValue([catalogOnlyProvider]);
|
||||
|
||||
|
||||
@@ -16,6 +16,10 @@ type AllModelRowSources = {
|
||||
useProviderCatalogFastPath: boolean;
|
||||
};
|
||||
|
||||
type AppendAllModelRowSourcesResult = {
|
||||
requiresRegistryFallback: boolean;
|
||||
};
|
||||
|
||||
export function modelRowSourcesRequireRegistry(params: {
|
||||
all?: boolean;
|
||||
providerFilter?: string;
|
||||
@@ -30,7 +34,9 @@ export function modelRowSourcesRequireRegistry(params: {
|
||||
return true;
|
||||
}
|
||||
|
||||
export async function appendAllModelRowSources(params: AllModelRowSources): Promise<void> {
|
||||
export async function appendAllModelRowSources(
|
||||
params: AllModelRowSources,
|
||||
): Promise<AppendAllModelRowSourcesResult> {
|
||||
if (params.context.filter.provider && params.useProviderCatalogFastPath) {
|
||||
let seenKeys = new Set<string>();
|
||||
appendConfiguredProviderRows({
|
||||
@@ -45,13 +51,16 @@ export async function appendAllModelRowSources(params: AllModelRowSources): Prom
|
||||
staticOnly: true,
|
||||
});
|
||||
if (catalogRows === 0) {
|
||||
seenKeys = appendDiscoveredRows({
|
||||
if (!params.modelRegistry) {
|
||||
return { requiresRegistryFallback: true };
|
||||
}
|
||||
appendDiscoveredRows({
|
||||
rows: params.rows,
|
||||
models: params.modelRegistry?.getAll() ?? [],
|
||||
models: params.modelRegistry.getAll(),
|
||||
context: params.context,
|
||||
});
|
||||
}
|
||||
return;
|
||||
return { requiresRegistryFallback: false };
|
||||
}
|
||||
|
||||
const seenKeys = appendDiscoveredRows({
|
||||
@@ -73,7 +82,7 @@ export async function appendAllModelRowSources(params: AllModelRowSources): Prom
|
||||
context: params.context,
|
||||
seenKeys,
|
||||
});
|
||||
return;
|
||||
return { requiresRegistryFallback: false };
|
||||
}
|
||||
|
||||
await appendProviderCatalogRows({
|
||||
@@ -81,6 +90,7 @@ export async function appendAllModelRowSources(params: AllModelRowSources): Prom
|
||||
context: params.context,
|
||||
seenKeys,
|
||||
});
|
||||
return { requiresRegistryFallback: false };
|
||||
}
|
||||
|
||||
export function appendConfiguredModelRowSources(params: {
|
||||
|
||||
Reference in New Issue
Block a user