mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-23 01:48:11 +00:00
fix: normalize provider aliases in model catalog merge
This commit is contained in:
@@ -162,4 +162,94 @@ describe("loadModelCatalog", () => {
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
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);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -2,6 +2,7 @@ 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");
|
||||
@@ -99,6 +100,25 @@ function normalizeInput(modalities: unknown): Array<"text" | "image"> | undefine
|
||||
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) {
|
||||
@@ -106,7 +126,7 @@ function readConfiguredModelsFromConfig(cfg: OpenClawConfig): ModelCatalogEntry[
|
||||
}
|
||||
const entries: ModelCatalogEntry[] = [];
|
||||
for (const [providerIdRaw, providerValue] of Object.entries(providers)) {
|
||||
const provider = String(providerIdRaw ?? "").trim();
|
||||
const provider = normalizeCatalogProvider(providerIdRaw);
|
||||
if (!provider || !providerValue) {
|
||||
continue;
|
||||
}
|
||||
@@ -140,12 +160,10 @@ function mergeMissingCatalogEntries(
|
||||
discoveredModels: ModelCatalogEntry[],
|
||||
configuredModels: ModelCatalogEntry[],
|
||||
): ModelCatalogEntry[] {
|
||||
const seen = new Set(
|
||||
discoveredModels.map((entry) => `${entry.provider.toLowerCase()}/${entry.id.toLowerCase()}`),
|
||||
);
|
||||
const seen = new Set(discoveredModels.map((entry) => modelCatalogMergeKey(entry)));
|
||||
const merged = [...discoveredModels];
|
||||
for (const entry of configuredModels) {
|
||||
const key = `${entry.provider.toLowerCase()}/${entry.id.toLowerCase()}`;
|
||||
const key = modelCatalogMergeKey(entry);
|
||||
if (seen.has(key)) {
|
||||
continue;
|
||||
}
|
||||
@@ -205,7 +223,7 @@ export async function loadModelCatalog(params?: {
|
||||
if (!id) {
|
||||
continue;
|
||||
}
|
||||
const provider = String(entry?.provider ?? "").trim();
|
||||
const provider = normalizeCatalogProvider(entry?.provider);
|
||||
if (!provider) {
|
||||
continue;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user