mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-23 01:48:11 +00:00
revert: keep model-catalog behavior unchanged
This commit is contained in:
@@ -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);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user