mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-08 01:21:25 +00:00
Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
This commit is contained in:
@@ -92,9 +92,9 @@ const AUTH_CHOICE_GROUP_DEFS: {
|
||||
},
|
||||
{
|
||||
value: "zai",
|
||||
label: "Z.AI (GLM 4.7)",
|
||||
hint: "API key",
|
||||
choices: ["zai-api-key"],
|
||||
label: "Z.AI",
|
||||
hint: "GLM Coding Plan / Global / CN",
|
||||
choices: ["zai-coding-global", "zai-coding-cn", "zai-global", "zai-cn"],
|
||||
},
|
||||
{
|
||||
value: "qianfan",
|
||||
@@ -242,7 +242,27 @@ export function buildAuthChoiceOptions(params: {
|
||||
label: "Google Gemini CLI OAuth",
|
||||
hint: "Uses the bundled Gemini CLI auth plugin",
|
||||
});
|
||||
options.push({ value: "zai-api-key", label: "Z.AI (GLM 4.7) API key" });
|
||||
options.push({ value: "zai-api-key", label: "Z.AI API key" });
|
||||
options.push({
|
||||
value: "zai-coding-global",
|
||||
label: "Coding-Plan-Global",
|
||||
hint: "GLM Coding Plan Global (api.z.ai)",
|
||||
});
|
||||
options.push({
|
||||
value: "zai-coding-cn",
|
||||
label: "Coding-Plan-CN",
|
||||
hint: "GLM Coding Plan CN (open.bigmodel.cn)",
|
||||
});
|
||||
options.push({
|
||||
value: "zai-global",
|
||||
label: "Global",
|
||||
hint: "Z.AI Global (api.z.ai)",
|
||||
});
|
||||
options.push({
|
||||
value: "zai-cn",
|
||||
label: "CN",
|
||||
hint: "Z.AI CN (open.bigmodel.cn)",
|
||||
});
|
||||
options.push({
|
||||
value: "xiaomi-api-key",
|
||||
label: "Xiaomi API key",
|
||||
|
||||
@@ -40,6 +40,7 @@ import {
|
||||
applyXiaomiConfig,
|
||||
applyXiaomiProviderConfig,
|
||||
applyZaiConfig,
|
||||
applyZaiProviderConfig,
|
||||
CLOUDFLARE_AI_GATEWAY_DEFAULT_MODEL_REF,
|
||||
LITELLM_DEFAULT_MODEL_REF,
|
||||
QIANFAN_DEFAULT_MODEL_REF,
|
||||
@@ -619,7 +620,54 @@ export async function applyAuthChoiceApiProviders(
|
||||
return { config: nextConfig, agentModelOverride };
|
||||
}
|
||||
|
||||
if (authChoice === "zai-api-key") {
|
||||
if (
|
||||
authChoice === "zai-api-key" ||
|
||||
authChoice === "zai-coding-global" ||
|
||||
authChoice === "zai-coding-cn" ||
|
||||
authChoice === "zai-global" ||
|
||||
authChoice === "zai-cn"
|
||||
) {
|
||||
// Determine endpoint from authChoice or prompt
|
||||
let endpoint: string;
|
||||
if (authChoice === "zai-coding-global") {
|
||||
endpoint = "coding-global";
|
||||
} else if (authChoice === "zai-coding-cn") {
|
||||
endpoint = "coding-cn";
|
||||
} else if (authChoice === "zai-global") {
|
||||
endpoint = "global";
|
||||
} else if (authChoice === "zai-cn") {
|
||||
endpoint = "cn";
|
||||
} else {
|
||||
// zai-api-key: prompt for endpoint selection
|
||||
endpoint = await params.prompter.select({
|
||||
message: "Select Z.AI endpoint",
|
||||
options: [
|
||||
{
|
||||
value: "coding-global",
|
||||
label: "Coding-Plan-Global",
|
||||
hint: "GLM Coding Plan Global (api.z.ai)",
|
||||
},
|
||||
{
|
||||
value: "coding-cn",
|
||||
label: "Coding-Plan-CN",
|
||||
hint: "GLM Coding Plan CN (open.bigmodel.cn)",
|
||||
},
|
||||
{
|
||||
value: "global",
|
||||
label: "Global",
|
||||
hint: "Z.AI Global (api.z.ai)",
|
||||
},
|
||||
{
|
||||
value: "cn",
|
||||
label: "CN",
|
||||
hint: "Z.AI CN (open.bigmodel.cn)",
|
||||
},
|
||||
],
|
||||
initialValue: "coding-global",
|
||||
});
|
||||
}
|
||||
|
||||
// Input API key
|
||||
let hasCredential = false;
|
||||
|
||||
if (!hasCredential && params.opts?.token && params.opts?.tokenProvider === "zai") {
|
||||
@@ -655,23 +703,8 @@ export async function applyAuthChoiceApiProviders(
|
||||
config: nextConfig,
|
||||
setDefaultModel: params.setDefaultModel,
|
||||
defaultModel: ZAI_DEFAULT_MODEL_REF,
|
||||
applyDefaultConfig: applyZaiConfig,
|
||||
applyProviderConfig: (config) => ({
|
||||
...config,
|
||||
agents: {
|
||||
...config.agents,
|
||||
defaults: {
|
||||
...config.agents?.defaults,
|
||||
models: {
|
||||
...config.agents?.defaults?.models,
|
||||
[ZAI_DEFAULT_MODEL_REF]: {
|
||||
...config.agents?.defaults?.models?.[ZAI_DEFAULT_MODEL_REF],
|
||||
alias: config.agents?.defaults?.models?.[ZAI_DEFAULT_MODEL_REF]?.alias ?? "GLM",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
applyDefaultConfig: (config) => applyZaiConfig(config, { endpoint }),
|
||||
applyProviderConfig: (config) => applyZaiProviderConfig(config, { endpoint }),
|
||||
noteDefault: ZAI_DEFAULT_MODEL_REF,
|
||||
noteAgentModel,
|
||||
prompter: params.prompter,
|
||||
|
||||
@@ -20,6 +20,10 @@ const PREFERRED_PROVIDER_BY_AUTH_CHOICE: Partial<Record<AuthChoice, string>> = {
|
||||
"google-antigravity": "google-antigravity",
|
||||
"google-gemini-cli": "google-gemini-cli",
|
||||
"zai-api-key": "zai",
|
||||
"zai-coding-global": "zai",
|
||||
"zai-coding-cn": "zai",
|
||||
"zai-global": "zai",
|
||||
"zai-cn": "zai",
|
||||
"xiaomi-api-key": "xiaomi",
|
||||
"synthetic-api-key": "synthetic",
|
||||
"venice-api-key": "venice",
|
||||
|
||||
@@ -6,6 +6,7 @@ import type { RuntimeEnv } from "../runtime.js";
|
||||
import type { WizardPrompter } from "../wizard/prompts.js";
|
||||
import type { AuthChoice } from "./onboard-types.js";
|
||||
import { applyAuthChoice, resolvePreferredProviderForAuthChoice } from "./auth-choice.js";
|
||||
import { ZAI_CODING_CN_BASE_URL, ZAI_CODING_GLOBAL_BASE_URL } from "./onboard-auth.js";
|
||||
|
||||
vi.mock("../providers/github-copilot-auth.js", () => ({
|
||||
githubCopilotLoginCommand: vi.fn(async () => {}),
|
||||
@@ -199,6 +200,101 @@ describe("applyAuthChoice", () => {
|
||||
expect(parsed.profiles?.["synthetic:default"]?.key).toBe("sk-synthetic-test");
|
||||
});
|
||||
|
||||
it("prompts for Z.AI endpoint when selecting zai-api-key", async () => {
|
||||
tempStateDir = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-auth-"));
|
||||
process.env.OPENCLAW_STATE_DIR = tempStateDir;
|
||||
process.env.OPENCLAW_AGENT_DIR = path.join(tempStateDir, "agent");
|
||||
process.env.PI_CODING_AGENT_DIR = process.env.OPENCLAW_AGENT_DIR;
|
||||
|
||||
const text = vi.fn().mockResolvedValue("zai-test-key");
|
||||
const select = vi.fn(async (params: { message: string }) => {
|
||||
if (params.message === "Select Z.AI endpoint") {
|
||||
return "coding-cn";
|
||||
}
|
||||
return "default";
|
||||
});
|
||||
const multiselect: WizardPrompter["multiselect"] = vi.fn(async () => []);
|
||||
const prompter: WizardPrompter = {
|
||||
intro: vi.fn(noopAsync),
|
||||
outro: vi.fn(noopAsync),
|
||||
note: vi.fn(noopAsync),
|
||||
select: select as WizardPrompter["select"],
|
||||
multiselect,
|
||||
text,
|
||||
confirm: vi.fn(async () => false),
|
||||
progress: vi.fn(() => ({ update: noop, stop: noop })),
|
||||
};
|
||||
const runtime: RuntimeEnv = {
|
||||
log: vi.fn(),
|
||||
error: vi.fn(),
|
||||
exit: vi.fn((code: number) => {
|
||||
throw new Error(`exit:${code}`);
|
||||
}),
|
||||
};
|
||||
|
||||
const result = await applyAuthChoice({
|
||||
authChoice: "zai-api-key",
|
||||
config: {},
|
||||
prompter,
|
||||
runtime,
|
||||
setDefaultModel: true,
|
||||
});
|
||||
|
||||
expect(select).toHaveBeenCalledWith(
|
||||
expect.objectContaining({ message: "Select Z.AI endpoint", initialValue: "coding-global" }),
|
||||
);
|
||||
expect(result.config.models?.providers?.zai?.baseUrl).toBe(ZAI_CODING_CN_BASE_URL);
|
||||
expect(result.config.agents?.defaults?.model?.primary).toBe("zai/glm-4.7");
|
||||
|
||||
const authProfilePath = authProfilePathFor(requireAgentDir());
|
||||
const raw = await fs.readFile(authProfilePath, "utf8");
|
||||
const parsed = JSON.parse(raw) as {
|
||||
profiles?: Record<string, { key?: string }>;
|
||||
};
|
||||
expect(parsed.profiles?.["zai:default"]?.key).toBe("zai-test-key");
|
||||
});
|
||||
|
||||
it("uses endpoint-specific auth choice without prompting for Z.AI endpoint", async () => {
|
||||
tempStateDir = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-auth-"));
|
||||
process.env.OPENCLAW_STATE_DIR = tempStateDir;
|
||||
process.env.OPENCLAW_AGENT_DIR = path.join(tempStateDir, "agent");
|
||||
process.env.PI_CODING_AGENT_DIR = process.env.OPENCLAW_AGENT_DIR;
|
||||
|
||||
const text = vi.fn().mockResolvedValue("zai-test-key");
|
||||
const select = vi.fn(async () => "default");
|
||||
const multiselect: WizardPrompter["multiselect"] = vi.fn(async () => []);
|
||||
const prompter: WizardPrompter = {
|
||||
intro: vi.fn(noopAsync),
|
||||
outro: vi.fn(noopAsync),
|
||||
note: vi.fn(noopAsync),
|
||||
select: select as WizardPrompter["select"],
|
||||
multiselect,
|
||||
text,
|
||||
confirm: vi.fn(async () => false),
|
||||
progress: vi.fn(() => ({ update: noop, stop: noop })),
|
||||
};
|
||||
const runtime: RuntimeEnv = {
|
||||
log: vi.fn(),
|
||||
error: vi.fn(),
|
||||
exit: vi.fn((code: number) => {
|
||||
throw new Error(`exit:${code}`);
|
||||
}),
|
||||
};
|
||||
|
||||
const result = await applyAuthChoice({
|
||||
authChoice: "zai-coding-global",
|
||||
config: {},
|
||||
prompter,
|
||||
runtime,
|
||||
setDefaultModel: true,
|
||||
});
|
||||
|
||||
expect(select).not.toHaveBeenCalledWith(
|
||||
expect.objectContaining({ message: "Select Z.AI endpoint" }),
|
||||
);
|
||||
expect(result.config.models?.providers?.zai?.baseUrl).toBe(ZAI_CODING_GLOBAL_BASE_URL);
|
||||
});
|
||||
|
||||
it("does not override the global default model when selecting xai-api-key without setDefaultModel", async () => {
|
||||
tempStateDir = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-auth-"));
|
||||
process.env.OPENCLAW_STATE_DIR = tempStateDir;
|
||||
|
||||
@@ -38,6 +38,7 @@ import {
|
||||
XAI_DEFAULT_MODEL_REF,
|
||||
} from "./onboard-auth.credentials.js";
|
||||
import {
|
||||
buildZaiModelDefinition,
|
||||
buildMoonshotModelDefinition,
|
||||
buildXaiModelDefinition,
|
||||
QIANFAN_BASE_URL,
|
||||
@@ -47,18 +48,65 @@ import {
|
||||
MOONSHOT_CN_BASE_URL,
|
||||
MOONSHOT_DEFAULT_MODEL_ID,
|
||||
MOONSHOT_DEFAULT_MODEL_REF,
|
||||
ZAI_DEFAULT_MODEL_ID,
|
||||
resolveZaiBaseUrl,
|
||||
XAI_BASE_URL,
|
||||
XAI_DEFAULT_MODEL_ID,
|
||||
} from "./onboard-auth.models.js";
|
||||
|
||||
export function applyZaiConfig(cfg: OpenClawConfig): OpenClawConfig {
|
||||
export function applyZaiProviderConfig(
|
||||
cfg: OpenClawConfig,
|
||||
params?: { endpoint?: string; modelId?: string },
|
||||
): OpenClawConfig {
|
||||
const modelId = params?.modelId?.trim() || ZAI_DEFAULT_MODEL_ID;
|
||||
const modelRef = `zai/${modelId}`;
|
||||
|
||||
const models = { ...cfg.agents?.defaults?.models };
|
||||
models[ZAI_DEFAULT_MODEL_REF] = {
|
||||
...models[ZAI_DEFAULT_MODEL_REF],
|
||||
alias: models[ZAI_DEFAULT_MODEL_REF]?.alias ?? "GLM",
|
||||
models[modelRef] = {
|
||||
...models[modelRef],
|
||||
alias: models[modelRef]?.alias ?? "GLM",
|
||||
};
|
||||
|
||||
const providers = { ...cfg.models?.providers };
|
||||
const existingProvider = providers.zai;
|
||||
const existingModels = Array.isArray(existingProvider?.models) ? existingProvider.models : [];
|
||||
|
||||
const defaultModels = [
|
||||
buildZaiModelDefinition({ id: "glm-5" }),
|
||||
buildZaiModelDefinition({ id: "glm-4.7" }),
|
||||
buildZaiModelDefinition({ id: "glm-4.7-flash" }),
|
||||
buildZaiModelDefinition({ id: "glm-4.7-flashx" }),
|
||||
];
|
||||
|
||||
const mergedModels = [...existingModels];
|
||||
const seen = new Set(existingModels.map((m) => m.id));
|
||||
for (const model of defaultModels) {
|
||||
if (!seen.has(model.id)) {
|
||||
mergedModels.push(model);
|
||||
seen.add(model.id);
|
||||
}
|
||||
}
|
||||
|
||||
const { apiKey: existingApiKey, ...existingProviderRest } = (existingProvider ?? {}) as Record<
|
||||
string,
|
||||
unknown
|
||||
> as { apiKey?: string };
|
||||
const resolvedApiKey = typeof existingApiKey === "string" ? existingApiKey : undefined;
|
||||
const normalizedApiKey = resolvedApiKey?.trim();
|
||||
|
||||
const baseUrl = params?.endpoint
|
||||
? resolveZaiBaseUrl(params.endpoint)
|
||||
: (typeof existingProvider?.baseUrl === "string" ? existingProvider.baseUrl : "") ||
|
||||
resolveZaiBaseUrl();
|
||||
|
||||
providers.zai = {
|
||||
...existingProviderRest,
|
||||
baseUrl,
|
||||
api: "openai-completions",
|
||||
...(normalizedApiKey ? { apiKey: normalizedApiKey } : {}),
|
||||
models: mergedModels.length > 0 ? mergedModels : defaultModels,
|
||||
};
|
||||
|
||||
const existingModel = cfg.agents?.defaults?.model;
|
||||
return {
|
||||
...cfg,
|
||||
agents: {
|
||||
@@ -66,13 +114,37 @@ export function applyZaiConfig(cfg: OpenClawConfig): OpenClawConfig {
|
||||
defaults: {
|
||||
...cfg.agents?.defaults,
|
||||
models,
|
||||
},
|
||||
},
|
||||
models: {
|
||||
mode: cfg.models?.mode ?? "merge",
|
||||
providers,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export function applyZaiConfig(
|
||||
cfg: OpenClawConfig,
|
||||
params?: { endpoint?: string; modelId?: string },
|
||||
): OpenClawConfig {
|
||||
const modelId = params?.modelId?.trim() || ZAI_DEFAULT_MODEL_ID;
|
||||
const modelRef = modelId === ZAI_DEFAULT_MODEL_ID ? ZAI_DEFAULT_MODEL_REF : `zai/${modelId}`;
|
||||
const next = applyZaiProviderConfig(cfg, params);
|
||||
|
||||
const existingModel = next.agents?.defaults?.model;
|
||||
return {
|
||||
...next,
|
||||
agents: {
|
||||
...next.agents,
|
||||
defaults: {
|
||||
...next.agents?.defaults,
|
||||
model: {
|
||||
...(existingModel && "fallbacks" in (existingModel as Record<string, unknown>)
|
||||
? {
|
||||
fallbacks: (existingModel as { fallbacks?: string[] }).fallbacks,
|
||||
}
|
||||
: undefined),
|
||||
primary: ZAI_DEFAULT_MODEL_REF,
|
||||
primary: modelRef,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -20,6 +20,26 @@ export const KIMI_CODING_MODEL_REF = `kimi-coding/${KIMI_CODING_MODEL_ID}`;
|
||||
export { QIANFAN_BASE_URL, QIANFAN_DEFAULT_MODEL_ID };
|
||||
export const QIANFAN_DEFAULT_MODEL_REF = `qianfan/${QIANFAN_DEFAULT_MODEL_ID}`;
|
||||
|
||||
export const ZAI_CODING_GLOBAL_BASE_URL = "https://api.z.ai/api/coding/paas/v4";
|
||||
export const ZAI_CODING_CN_BASE_URL = "https://open.bigmodel.cn/api/coding/paas/v4";
|
||||
export const ZAI_GLOBAL_BASE_URL = "https://api.z.ai/api/paas/v4";
|
||||
export const ZAI_CN_BASE_URL = "https://open.bigmodel.cn/api/paas/v4";
|
||||
export const ZAI_DEFAULT_MODEL_ID = "glm-4.7";
|
||||
|
||||
export function resolveZaiBaseUrl(endpoint?: string): string {
|
||||
switch (endpoint) {
|
||||
case "coding-cn":
|
||||
return ZAI_CODING_CN_BASE_URL;
|
||||
case "global":
|
||||
return ZAI_GLOBAL_BASE_URL;
|
||||
case "cn":
|
||||
return ZAI_CN_BASE_URL;
|
||||
case "coding-global":
|
||||
default:
|
||||
return ZAI_CODING_GLOBAL_BASE_URL;
|
||||
}
|
||||
}
|
||||
|
||||
// Pricing: MiniMax doesn't publish public rates. Override in models.json for accurate costs.
|
||||
export const MINIMAX_API_COST = {
|
||||
input: 15,
|
||||
@@ -46,6 +66,13 @@ export const MOONSHOT_DEFAULT_COST = {
|
||||
cacheWrite: 0,
|
||||
};
|
||||
|
||||
export const ZAI_DEFAULT_COST = {
|
||||
input: 0,
|
||||
output: 0,
|
||||
cacheRead: 0,
|
||||
cacheWrite: 0,
|
||||
};
|
||||
|
||||
const MINIMAX_MODEL_CATALOG = {
|
||||
"MiniMax-M2.1": { name: "MiniMax M2.1", reasoning: false },
|
||||
"MiniMax-M2.1-lightning": {
|
||||
@@ -56,6 +83,15 @@ const MINIMAX_MODEL_CATALOG = {
|
||||
|
||||
type MinimaxCatalogId = keyof typeof MINIMAX_MODEL_CATALOG;
|
||||
|
||||
const ZAI_MODEL_CATALOG = {
|
||||
"glm-5": { name: "GLM-5", reasoning: true },
|
||||
"glm-4.7": { name: "GLM-4.7", reasoning: true },
|
||||
"glm-4.7-flash": { name: "GLM-4.7 Flash", reasoning: true },
|
||||
"glm-4.7-flashx": { name: "GLM-4.7 FlashX", reasoning: true },
|
||||
} as const;
|
||||
|
||||
type ZaiCatalogId = keyof typeof ZAI_MODEL_CATALOG;
|
||||
|
||||
export function buildMinimaxModelDefinition(params: {
|
||||
id: string;
|
||||
name?: string;
|
||||
@@ -97,6 +133,26 @@ export function buildMoonshotModelDefinition(): ModelDefinitionConfig {
|
||||
};
|
||||
}
|
||||
|
||||
export function buildZaiModelDefinition(params: {
|
||||
id: string;
|
||||
name?: string;
|
||||
reasoning?: boolean;
|
||||
cost?: ModelDefinitionConfig["cost"];
|
||||
contextWindow?: number;
|
||||
maxTokens?: number;
|
||||
}): ModelDefinitionConfig {
|
||||
const catalog = ZAI_MODEL_CATALOG[params.id as ZaiCatalogId];
|
||||
return {
|
||||
id: params.id,
|
||||
name: params.name ?? catalog?.name ?? `GLM ${params.id}`,
|
||||
reasoning: params.reasoning ?? catalog?.reasoning ?? true,
|
||||
input: ["text"],
|
||||
cost: params.cost ?? ZAI_DEFAULT_COST,
|
||||
contextWindow: params.contextWindow ?? 204800,
|
||||
maxTokens: params.maxTokens ?? 131072,
|
||||
};
|
||||
}
|
||||
|
||||
export const XAI_BASE_URL = "https://api.x.ai/v1";
|
||||
export const XAI_DEFAULT_MODEL_ID = "grok-4";
|
||||
export const XAI_DEFAULT_MODEL_REF = `xai/${XAI_DEFAULT_MODEL_ID}`;
|
||||
|
||||
@@ -18,12 +18,16 @@ import {
|
||||
applyXaiProviderConfig,
|
||||
applyXiaomiConfig,
|
||||
applyXiaomiProviderConfig,
|
||||
applyZaiConfig,
|
||||
applyZaiProviderConfig,
|
||||
OPENROUTER_DEFAULT_MODEL_REF,
|
||||
SYNTHETIC_DEFAULT_MODEL_ID,
|
||||
SYNTHETIC_DEFAULT_MODEL_REF,
|
||||
XAI_DEFAULT_MODEL_REF,
|
||||
setMinimaxApiKey,
|
||||
writeOAuthCredentials,
|
||||
ZAI_CODING_CN_BASE_URL,
|
||||
ZAI_CODING_GLOBAL_BASE_URL,
|
||||
} from "./onboard-auth.js";
|
||||
|
||||
const authProfilePathFor = (agentDir: string) => path.join(agentDir, "auth-profiles.json");
|
||||
@@ -303,6 +307,47 @@ describe("applyMinimaxApiProviderConfig", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("applyZaiConfig", () => {
|
||||
it("adds zai provider with correct settings", () => {
|
||||
const cfg = applyZaiConfig({});
|
||||
expect(cfg.models?.providers?.zai).toMatchObject({
|
||||
baseUrl: ZAI_CODING_GLOBAL_BASE_URL,
|
||||
api: "openai-completions",
|
||||
});
|
||||
const ids = cfg.models?.providers?.zai?.models?.map((m) => m.id);
|
||||
expect(ids).toContain("glm-5");
|
||||
expect(ids).toContain("glm-4.7");
|
||||
expect(ids).toContain("glm-4.7-flash");
|
||||
expect(ids).toContain("glm-4.7-flashx");
|
||||
});
|
||||
|
||||
it("sets correct primary model", () => {
|
||||
const cfg = applyZaiConfig({}, { modelId: "glm-5" });
|
||||
expect(cfg.agents?.defaults?.model?.primary).toBe("zai/glm-5");
|
||||
});
|
||||
|
||||
it("supports CN endpoint", () => {
|
||||
const cfg = applyZaiConfig({}, { endpoint: "coding-cn", modelId: "glm-4.7-flash" });
|
||||
expect(cfg.models?.providers?.zai?.baseUrl).toBe(ZAI_CODING_CN_BASE_URL);
|
||||
expect(cfg.agents?.defaults?.model?.primary).toBe("zai/glm-4.7-flash");
|
||||
});
|
||||
|
||||
it("supports CN endpoint with glm-4.7-flashx", () => {
|
||||
const cfg = applyZaiConfig({}, { endpoint: "coding-cn", modelId: "glm-4.7-flashx" });
|
||||
expect(cfg.models?.providers?.zai?.baseUrl).toBe(ZAI_CODING_CN_BASE_URL);
|
||||
expect(cfg.agents?.defaults?.model?.primary).toBe("zai/glm-4.7-flashx");
|
||||
});
|
||||
});
|
||||
|
||||
describe("applyZaiProviderConfig", () => {
|
||||
it("does not overwrite existing primary model", () => {
|
||||
const cfg = applyZaiProviderConfig({
|
||||
agents: { defaults: { model: { primary: "anthropic/claude-opus-4-5" } } },
|
||||
});
|
||||
expect(cfg.agents?.defaults?.model?.primary).toBe("anthropic/claude-opus-4-5");
|
||||
});
|
||||
});
|
||||
|
||||
describe("applySyntheticConfig", () => {
|
||||
it("adds synthetic provider with correct settings", () => {
|
||||
const cfg = applySyntheticConfig({});
|
||||
|
||||
@@ -32,6 +32,7 @@ export {
|
||||
applyXiaomiConfig,
|
||||
applyXiaomiProviderConfig,
|
||||
applyZaiConfig,
|
||||
applyZaiProviderConfig,
|
||||
} from "./onboard-auth.config-core.js";
|
||||
export {
|
||||
applyMinimaxApiConfig,
|
||||
@@ -78,6 +79,7 @@ export {
|
||||
buildMinimaxApiModelDefinition,
|
||||
buildMinimaxModelDefinition,
|
||||
buildMoonshotModelDefinition,
|
||||
buildZaiModelDefinition,
|
||||
DEFAULT_MINIMAX_BASE_URL,
|
||||
MOONSHOT_CN_BASE_URL,
|
||||
QIANFAN_BASE_URL,
|
||||
@@ -91,4 +93,10 @@ export {
|
||||
MOONSHOT_BASE_URL,
|
||||
MOONSHOT_DEFAULT_MODEL_ID,
|
||||
MOONSHOT_DEFAULT_MODEL_REF,
|
||||
resolveZaiBaseUrl,
|
||||
ZAI_CODING_CN_BASE_URL,
|
||||
ZAI_DEFAULT_MODEL_ID,
|
||||
ZAI_CODING_GLOBAL_BASE_URL,
|
||||
ZAI_CN_BASE_URL,
|
||||
ZAI_GLOBAL_BASE_URL,
|
||||
} from "./onboard-auth.models.js";
|
||||
|
||||
@@ -139,6 +139,60 @@ async function expectApiKeyProfile(params: {
|
||||
}
|
||||
|
||||
describe("onboard (non-interactive): provider auth", () => {
|
||||
it("stores Z.AI API key and uses coding-global baseUrl by default", async () => {
|
||||
await withOnboardEnv("openclaw-onboard-zai-", async ({ configPath, runtime }) => {
|
||||
await runNonInteractive(
|
||||
{
|
||||
nonInteractive: true,
|
||||
authChoice: "zai-api-key",
|
||||
zaiApiKey: "zai-test-key",
|
||||
skipHealth: true,
|
||||
skipChannels: true,
|
||||
skipSkills: true,
|
||||
json: true,
|
||||
},
|
||||
runtime,
|
||||
);
|
||||
|
||||
const cfg = await readJsonFile<{
|
||||
auth?: { profiles?: Record<string, { provider?: string; mode?: string }> };
|
||||
agents?: { defaults?: { model?: { primary?: string } } };
|
||||
models?: { providers?: Record<string, { baseUrl?: string }> };
|
||||
}>(configPath);
|
||||
|
||||
expect(cfg.auth?.profiles?.["zai:default"]?.provider).toBe("zai");
|
||||
expect(cfg.auth?.profiles?.["zai:default"]?.mode).toBe("api_key");
|
||||
expect(cfg.models?.providers?.zai?.baseUrl).toBe("https://api.z.ai/api/coding/paas/v4");
|
||||
expect(cfg.agents?.defaults?.model?.primary).toBe("zai/glm-4.7");
|
||||
await expectApiKeyProfile({ profileId: "zai:default", provider: "zai", key: "zai-test-key" });
|
||||
});
|
||||
}, 60_000);
|
||||
|
||||
it("supports Z.AI CN coding endpoint auth choice", async () => {
|
||||
await withOnboardEnv("openclaw-onboard-zai-cn-", async ({ configPath, runtime }) => {
|
||||
await runNonInteractive(
|
||||
{
|
||||
nonInteractive: true,
|
||||
authChoice: "zai-coding-cn",
|
||||
zaiApiKey: "zai-test-key",
|
||||
skipHealth: true,
|
||||
skipChannels: true,
|
||||
skipSkills: true,
|
||||
json: true,
|
||||
},
|
||||
runtime,
|
||||
);
|
||||
|
||||
const cfg = await readJsonFile<{
|
||||
models?: { providers?: Record<string, { baseUrl?: string }> };
|
||||
}>(configPath);
|
||||
|
||||
expect(cfg.models?.providers?.zai?.baseUrl).toBe(
|
||||
"https://open.bigmodel.cn/api/coding/paas/v4",
|
||||
);
|
||||
});
|
||||
}, 60_000);
|
||||
|
||||
it("stores xAI API key and sets default model", async () => {
|
||||
await withOnboardEnv("openclaw-onboard-xai-", async ({ configPath, runtime }) => {
|
||||
await runNonInteractive(
|
||||
|
||||
@@ -187,7 +187,13 @@ export async function applyNonInteractiveAuthChoice(params: {
|
||||
return applyGoogleGeminiModelDefault(nextConfig).next;
|
||||
}
|
||||
|
||||
if (authChoice === "zai-api-key") {
|
||||
if (
|
||||
authChoice === "zai-api-key" ||
|
||||
authChoice === "zai-coding-global" ||
|
||||
authChoice === "zai-coding-cn" ||
|
||||
authChoice === "zai-global" ||
|
||||
authChoice === "zai-cn"
|
||||
) {
|
||||
const resolved = await resolveNonInteractiveApiKey({
|
||||
provider: "zai",
|
||||
cfg: baseConfig,
|
||||
@@ -207,7 +213,21 @@ export async function applyNonInteractiveAuthChoice(params: {
|
||||
provider: "zai",
|
||||
mode: "api_key",
|
||||
});
|
||||
return applyZaiConfig(nextConfig);
|
||||
|
||||
// Determine endpoint from authChoice or opts
|
||||
let endpoint: "global" | "cn" | "coding-global" | "coding-cn" | undefined;
|
||||
if (authChoice === "zai-coding-global") {
|
||||
endpoint = "coding-global";
|
||||
} else if (authChoice === "zai-coding-cn") {
|
||||
endpoint = "coding-cn";
|
||||
} else if (authChoice === "zai-global") {
|
||||
endpoint = "global";
|
||||
} else if (authChoice === "zai-cn") {
|
||||
endpoint = "cn";
|
||||
} else {
|
||||
endpoint = "coding-global";
|
||||
}
|
||||
return applyZaiConfig(nextConfig, { endpoint });
|
||||
}
|
||||
|
||||
if (authChoice === "xiaomi-api-key") {
|
||||
|
||||
@@ -27,6 +27,10 @@ export type AuthChoice =
|
||||
| "google-antigravity"
|
||||
| "google-gemini-cli"
|
||||
| "zai-api-key"
|
||||
| "zai-coding-global"
|
||||
| "zai-coding-cn"
|
||||
| "zai-global"
|
||||
| "zai-cn"
|
||||
| "xiaomi-api-key"
|
||||
| "minimax-cloud"
|
||||
| "minimax"
|
||||
|
||||
Reference in New Issue
Block a user