Onboarding: preserve provider rewrites

This commit is contained in:
Alexander Davydov 2026-03-19 18:35:59 +03:00
parent d6daa108e3
commit 181031856d
2 changed files with 121 additions and 84 deletions

View File

@ -0,0 +1,97 @@
import { describe, expect, it } from "vitest";
import { SYNTHETIC_DEFAULT_MODEL_ID } from "../agents/synthetic-models.js";
import type { OpenClawConfig } from "../config/config.js";
import type { ModelDefinitionConfig } from "../config/types.models.js";
import {
applyModelStudioProviderConfig,
applySyntheticProviderConfig,
applyZaiProviderConfig,
} from "./onboard-auth.config-core.js";
function makeModel(id: string): ModelDefinitionConfig {
return {
id,
name: id,
reasoning: false,
input: ["text"],
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
contextWindow: 4096,
maxTokens: 1024,
};
}
describe("core onboarding provider rewriters", () => {
const secretRef = {
source: "env" as const,
provider: "default",
id: "TEST_API_KEY",
};
it("rewrites aliased ZAI providers to the canonical key and preserves secret refs", () => {
const cfg: OpenClawConfig = {
models: {
providers: {
"z-ai": {
api: "openai-completions",
baseUrl: "https://zai-preview.example/v1",
apiKey: secretRef,
models: [makeModel("legacy-zai-model")],
},
},
},
};
const result = applyZaiProviderConfig(cfg);
expect(Object.keys(result.models?.providers ?? {})).toEqual(["zai"]);
expect(result.models?.providers?.zai?.baseUrl).toBe("https://zai-preview.example/v1");
expect(result.models?.providers?.zai?.apiKey).toEqual(secretRef);
expect(result.models?.providers?.zai?.models?.map((model) => model.id)).toEqual(
expect.arrayContaining(["legacy-zai-model", "glm-5"]),
);
});
it("preserves secret-ref api keys when rewriting Synthetic", () => {
const cfg: OpenClawConfig = {
models: {
providers: {
synthetic: {
api: "openai-completions",
baseUrl: "https://synthetic-legacy.example/v1",
apiKey: secretRef,
models: [makeModel("legacy-synthetic-model")],
},
},
},
};
const result = applySyntheticProviderConfig(cfg);
expect(result.models?.providers?.synthetic?.apiKey).toEqual(secretRef);
expect(result.models?.providers?.synthetic?.models?.map((model) => model.id)).toEqual(
expect.arrayContaining(["legacy-synthetic-model", SYNTHETIC_DEFAULT_MODEL_ID]),
);
});
it("preserves secret-ref api keys when rewriting Model Studio", () => {
const cfg: OpenClawConfig = {
models: {
providers: {
modelstudio: {
api: "openai-completions",
baseUrl: "https://modelstudio-legacy.example/v1",
apiKey: secretRef,
models: [makeModel("legacy-modelstudio-model")],
},
},
},
};
const result = applyModelStudioProviderConfig(cfg);
expect(result.models?.providers?.modelstudio?.apiKey).toEqual(secretRef);
expect(result.models?.providers?.modelstudio?.models?.map((model) => model.id)).toEqual(
expect.arrayContaining(["legacy-modelstudio-model", "qwen3.5-plus"]),
);
});
});

View File

@ -15,6 +15,7 @@ import { findNormalizedProviderValue } from "../agents/provider-id.js";
import {
buildSyntheticModelDefinition,
SYNTHETIC_BASE_URL,
SYNTHETIC_DEFAULT_MODEL_ID,
SYNTHETIC_DEFAULT_MODEL_REF,
SYNTHETIC_MODEL_CATALOG,
} from "../agents/synthetic-models.js";
@ -57,7 +58,6 @@ export {
} from "./onboard-auth.config-litellm.js";
import {
applyAgentDefaultModelPrimary,
applyOnboardAuthAgentModelsAndProviders,
applyProviderConfigWithDefaultModel,
applyProviderConfigWithDefaultModels,
applyProviderConfigWithModelCatalog,
@ -91,29 +91,6 @@ import {
} from "./onboard-auth.models.js";
export { applyAuthProfileConfig } from "./auth-profile-config.js";
function mergeProviderModels<T extends { id: string }>(
existingProvider: Record<string, unknown> | undefined,
defaultModels: T[],
): T[] {
const existingModels = Array.isArray(existingProvider?.models)
? (existingProvider.models as T[])
: [];
const mergedModels = [...existingModels];
const seen = new Set(existingModels.map((model) => model.id));
for (const model of defaultModels) {
if (!seen.has(model.id)) {
mergedModels.push(model);
seen.add(model.id);
}
}
return mergedModels;
}
function getNormalizedProviderApiKey(existingProvider: Record<string, unknown> | undefined) {
const { apiKey } = (existingProvider ?? {}) as { apiKey?: string };
return typeof apiKey === "string" ? apiKey.trim() || undefined : undefined;
}
export function applyZaiProviderConfig(
cfg: OpenClawConfig,
params?: { endpoint?: string; modelId?: string },
@ -126,9 +103,7 @@ export function applyZaiProviderConfig(
...models[modelRef],
alias: models[modelRef]?.alias ?? "GLM",
};
const providers = { ...cfg.models?.providers };
const existingProvider = providers.zai;
const existingProvider = findNormalizedProviderValue(cfg.models?.providers, "zai");
const defaultModels = [
buildZaiModelDefinition({ id: "glm-5" }),
@ -138,28 +113,19 @@ export function applyZaiProviderConfig(
buildZaiModelDefinition({ id: "glm-4.7-flashx" }),
];
const mergedModels = mergeProviderModels(existingProvider, defaultModels);
const { apiKey: _existingApiKey, ...existingProviderRest } = (existingProvider ?? {}) as Record<
string,
unknown
> as { apiKey?: string };
const normalizedApiKey = getNormalizedProviderApiKey(existingProvider);
const baseUrl = params?.endpoint
? resolveZaiBaseUrl(params.endpoint)
: (typeof existingProvider?.baseUrl === "string" ? existingProvider.baseUrl : "") ||
resolveZaiBaseUrl();
providers.zai = {
...existingProviderRest,
baseUrl,
return applyProviderConfigWithDefaultModels(cfg, {
agentModels: models,
providerId: "zai",
api: "openai-completions",
...(normalizedApiKey ? { apiKey: normalizedApiKey } : {}),
models: mergedModels.length > 0 ? mergedModels : defaultModels,
};
return applyOnboardAuthAgentModelsAndProviders(cfg, { agentModels: models, providers });
baseUrl,
defaultModels,
defaultModelId: modelId,
});
}
export function applyZaiConfig(
@ -267,30 +233,16 @@ export function applySyntheticProviderConfig(cfg: OpenClawConfig): OpenClawConfi
alias: models[SYNTHETIC_DEFAULT_MODEL_REF]?.alias ?? "MiniMax M2.5",
};
const providers = { ...cfg.models?.providers };
const existingProvider = providers.synthetic;
const existingModels = Array.isArray(existingProvider?.models) ? existingProvider.models : [];
const syntheticModels = SYNTHETIC_MODEL_CATALOG.map(buildSyntheticModelDefinition);
const mergedModels = [
...existingModels,
...syntheticModels.filter(
(model) => !existingModels.some((existing) => existing.id === model.id),
),
];
const { apiKey: _existingApiKey, ...existingProviderRest } = (existingProvider ?? {}) as Record<
string,
unknown
> as { apiKey?: string };
const normalizedApiKey = getNormalizedProviderApiKey(existingProvider);
providers.synthetic = {
...existingProviderRest,
baseUrl: SYNTHETIC_BASE_URL,
api: "anthropic-messages",
...(normalizedApiKey ? { apiKey: normalizedApiKey } : {}),
models: mergedModels.length > 0 ? mergedModels : syntheticModels,
};
return applyOnboardAuthAgentModelsAndProviders(cfg, { agentModels: models, providers });
return applyProviderConfigWithDefaultModels(cfg, {
agentModels: models,
providerId: "synthetic",
api: "anthropic-messages",
baseUrl: SYNTHETIC_BASE_URL,
defaultModels: syntheticModels,
defaultModelId: SYNTHETIC_DEFAULT_MODEL_ID,
});
}
export function applySyntheticConfig(cfg: OpenClawConfig): OpenClawConfig {
@ -590,9 +542,6 @@ function applyModelStudioProviderConfigWithBaseUrl(
alias: models[MODELSTUDIO_DEFAULT_MODEL_REF]?.alias ?? "Qwen",
};
const providers = { ...cfg.models?.providers };
const existingProvider = providers.modelstudio;
const defaultModels = [
buildModelStudioModelDefinition({ id: "qwen3.5-plus" }),
buildModelStudioModelDefinition({ id: "qwen3-max-2026-01-23" }),
@ -604,23 +553,14 @@ function applyModelStudioProviderConfigWithBaseUrl(
buildModelStudioModelDefinition({ id: "kimi-k2.5" }),
];
const mergedModels = mergeProviderModels(existingProvider, defaultModels);
const { apiKey: _existingApiKey, ...existingProviderRest } = (existingProvider ?? {}) as Record<
string,
unknown
> as { apiKey?: string };
const normalizedApiKey = getNormalizedProviderApiKey(existingProvider);
providers.modelstudio = {
...existingProviderRest,
baseUrl,
return applyProviderConfigWithDefaultModels(cfg, {
agentModels: models,
providerId: "modelstudio",
api: "openai-completions",
...(normalizedApiKey ? { apiKey: normalizedApiKey } : {}),
models: mergedModels.length > 0 ? mergedModels : defaultModels,
};
return applyOnboardAuthAgentModelsAndProviders(cfg, { agentModels: models, providers });
baseUrl,
defaultModels,
defaultModelId: "qwen3.5-plus",
});
}
export function applyModelStudioProviderConfig(cfg: OpenClawConfig): OpenClawConfig {