revert: keep model-catalog behavior unchanged

This commit is contained in:
Gustavo Madeira Santana
2026-02-23 20:38:18 -05:00
parent d0143b0029
commit de1e527e14
2 changed files with 4 additions and 251 deletions

View File

@@ -103,153 +103,4 @@ describe("loadModelCatalog", () => {
expect(spark?.name).toBe("gpt-5.3-codex-spark");
expect(spark?.reasoning).toBe(true);
});
it("includes configured provider models from models.json when registry omits them", async () => {
const cfg = {
models: {
providers: {
kilocode: {
baseUrl: "https://api.kilo.ai/api/gateway/",
api: "openai-completions",
models: [
{
id: "anthropic/claude-opus-4.6",
name: "Claude Opus 4.6",
reasoning: true,
input: ["text", "image"],
contextWindow: 200000,
maxTokens: 8192,
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
},
{
id: "openai/gpt-5.2",
name: "GPT-5.2",
reasoning: true,
input: ["text"],
contextWindow: 400000,
maxTokens: 8192,
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
},
],
},
},
},
} satisfies OpenClawConfig;
__setModelCatalogImportForTest(
async () =>
({
AuthStorage: class {},
ModelRegistry: class {
getAll() {
return [{ id: "gpt-4.1", name: "GPT-4.1", provider: "openai" }];
}
},
}) as unknown as PiSdkModule,
);
const result = await loadModelCatalog({ config: cfg });
expect(result).toContainEqual(
expect.objectContaining({
provider: "kilocode",
id: "anthropic/claude-opus-4.6",
}),
);
expect(result).toContainEqual(
expect.objectContaining({
provider: "kilocode",
id: "openai/gpt-5.2",
}),
);
});
it("normalizes configured provider aliases in merged catalog entries", async () => {
const cfg = {
models: {
providers: {
"z-ai": {
baseUrl: "https://api.z.ai/api/paas/v4/",
api: "openai-completions",
models: [
{
id: "glm-5:free",
name: "GLM-5 (Free)",
reasoning: true,
input: ["text"],
contextWindow: 202800,
maxTokens: 131072,
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
},
],
},
},
},
} satisfies OpenClawConfig;
__setModelCatalogImportForTest(
async () =>
({
AuthStorage: class {},
ModelRegistry: class {
getAll() {
return [];
}
},
}) as unknown as PiSdkModule,
);
const result = await loadModelCatalog({ config: cfg });
expect(result).toContainEqual(
expect.objectContaining({
provider: "zai",
id: "glm-5:free",
}),
);
});
it("dedupes discovered and configured entries using normalized provider aliases", async () => {
const cfg = {
models: {
providers: {
"z-ai": {
baseUrl: "https://api.z.ai/api/paas/v4/",
api: "openai-completions",
models: [
{
id: "glm-5:free",
name: "GLM-5 (Free)",
reasoning: true,
input: ["text"],
contextWindow: 202800,
maxTokens: 131072,
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
},
],
},
},
},
} satisfies OpenClawConfig;
__setModelCatalogImportForTest(
async () =>
({
AuthStorage: class {},
ModelRegistry: class {
getAll() {
return [
{
id: "glm-5:free",
name: "GLM-5 (Free)",
provider: "zai",
},
];
}
},
}) as unknown as PiSdkModule,
);
const result = await loadModelCatalog({ config: cfg });
const matches = result.filter((entry) => entry.provider === "zai" && entry.id === "glm-5:free");
expect(matches).toHaveLength(1);
});
});

View File

@@ -1,8 +1,6 @@
import path from "node:path";
import { type OpenClawConfig, loadConfig } from "../config/config.js";
import { createSubsystemLogger } from "../logging/subsystem.js";
import { resolveOpenClawAgentDir } from "./agent-paths.js";
import { normalizeProviderId } from "./model-selection.js";
import { ensureOpenClawModelsJson } from "./models-config.js";
const log = createSubsystemLogger("model-catalog");
@@ -25,14 +23,6 @@ type DiscoveredModel = {
input?: Array<"text" | "image">;
};
type ConfigModelEntry = {
id?: unknown;
name?: unknown;
contextWindow?: unknown;
reasoning?: unknown;
input?: unknown;
};
type PiSdkModule = typeof import("./pi-model-discovery.js");
let modelCatalogPromise: Promise<ModelCatalogEntry[]> | null = null;
@@ -88,91 +78,6 @@ function createAuthStorage(AuthStorageLike: unknown, path: string) {
return new (AuthStorageLike as { new (path: string): unknown })(path);
}
function toPositiveNumber(value: unknown): number | undefined {
return typeof value === "number" && Number.isFinite(value) && value > 0 ? value : undefined;
}
function normalizeInput(modalities: unknown): Array<"text" | "image"> | undefined {
if (!Array.isArray(modalities) || modalities.length === 0) {
return undefined;
}
const hasImage = modalities.some((value) => String(value ?? "").toLowerCase() === "image");
return hasImage ? ["text", "image"] : ["text"];
}
function normalizeCatalogProvider(value: unknown): string {
if (typeof value !== "string") {
return "";
}
const trimmed = value.trim();
if (!trimmed) {
return "";
}
return normalizeProviderId(trimmed);
}
function modelCatalogMergeKey(entry: Pick<ModelCatalogEntry, "provider" | "id">): string {
const provider = normalizeCatalogProvider(entry.provider);
const id = String(entry.id ?? "")
.trim()
.toLowerCase();
return `${provider}/${id}`;
}
function readConfiguredModelsFromConfig(cfg: OpenClawConfig): ModelCatalogEntry[] {
const providers = cfg.models?.providers;
if (!providers) {
return [];
}
const entries: ModelCatalogEntry[] = [];
for (const [providerIdRaw, providerValue] of Object.entries(providers)) {
const provider = normalizeCatalogProvider(providerIdRaw);
if (!provider || !providerValue) {
continue;
}
const providerModels = (providerValue as { models?: unknown }).models;
if (!Array.isArray(providerModels)) {
continue;
}
for (const modelValue of providerModels) {
if (!modelValue || typeof modelValue !== "object") {
continue;
}
const model = modelValue as ConfigModelEntry;
if (typeof model.id !== "string") {
continue;
}
const id = model.id.trim();
if (!id) {
continue;
}
const name = typeof model.name === "string" && model.name.trim() ? model.name.trim() : id;
const contextWindow = toPositiveNumber(model.contextWindow);
const reasoning = typeof model.reasoning === "boolean" ? model.reasoning : undefined;
const input = normalizeInput(model.input);
entries.push({ id, name, provider, contextWindow, reasoning, input });
}
}
return entries;
}
function mergeMissingCatalogEntries(
discoveredModels: ModelCatalogEntry[],
configuredModels: ModelCatalogEntry[],
): ModelCatalogEntry[] {
const seen = new Set(discoveredModels.map((entry) => modelCatalogMergeKey(entry)));
const merged = [...discoveredModels];
for (const entry of configuredModels) {
const key = modelCatalogMergeKey(entry);
if (seen.has(key)) {
continue;
}
seen.add(key);
merged.push(entry);
}
return merged;
}
export async function loadModelCatalog(params?: {
config?: OpenClawConfig;
useCache?: boolean;
@@ -206,7 +111,8 @@ export async function loadModelCatalog(params?: {
// will keep failing until restart).
const piSdk = await importPiSdk();
const agentDir = resolveOpenClawAgentDir();
const authStorage = createAuthStorage(piSdk.AuthStorage, path.join(agentDir, "auth.json"));
const { join } = await import("node:path");
const authStorage = createAuthStorage(piSdk.AuthStorage, join(agentDir, "auth.json"));
const registry = new (piSdk.ModelRegistry as unknown as {
new (
authStorage: unknown,
@@ -216,14 +122,14 @@ export async function loadModelCatalog(params?: {
| {
getAll: () => Array<DiscoveredModel>;
};
})(authStorage, path.join(agentDir, "models.json"));
})(authStorage, join(agentDir, "models.json"));
const entries = Array.isArray(registry) ? registry : registry.getAll();
for (const entry of entries) {
const id = String(entry?.id ?? "").trim();
if (!id) {
continue;
}
const provider = normalizeCatalogProvider(entry?.provider);
const provider = String(entry?.provider ?? "").trim();
if (!provider) {
continue;
}
@@ -236,10 +142,6 @@ export async function loadModelCatalog(params?: {
const input = Array.isArray(entry?.input) ? entry.input : undefined;
models.push({ id, name, provider, contextWindow, reasoning, input });
}
const configuredModels = readConfiguredModelsFromConfig(cfg);
const mergedModels = mergeMissingCatalogEntries(models, configuredModels);
models.length = 0;
models.push(...mergedModels);
applyOpenAICodexSparkFallback(models);
if (models.length === 0) {