From 911f04a674caad0f8f7362c54d9ca1c44dcd314b Mon Sep 17 00:00:00 2001 From: Weixuan Xiao Date: Thu, 29 Jan 2026 15:49:19 +0100 Subject: [PATCH 1/2] Allow to set z.ai base URL to use other GLM endpoints --- docs/gateway/configuration.md | 2 + src/agents/models-config.providers.ts | 17 +++++ ...dels-config.providers.zai-base-url.test.ts | 65 +++++++++++++++++++ 3 files changed, 84 insertions(+) create mode 100644 src/agents/models-config.providers.zai-base-url.test.ts diff --git a/docs/gateway/configuration.md b/docs/gateway/configuration.md index 1d270974d..48a56bb8d 100644 --- a/docs/gateway/configuration.md +++ b/docs/gateway/configuration.md @@ -2380,6 +2380,8 @@ Notes: - `z.ai/*` and `z-ai/*` are accepted aliases and normalize to `zai/*`. - If `ZAI_API_KEY` is missing, requests to `zai/*` will fail with an auth error at runtime. - Example error: `No API key found for provider "zai".` +- Optional: set `ZAI_BASE_URL` to override the `zai` provider endpoint without editing config. + (Legacy alias: `Z_AI_BASE_URL`.) Example for Chinese Z.AI endpoint: `ZAI_BASE_URL=https://open.bigmodel.cn/api/paas/v4/` - Z.AI’s general API endpoint is `https://api.z.ai/api/paas/v4`. GLM coding requests use the dedicated Coding endpoint `https://api.z.ai/api/coding/paas/v4`. The built-in `zai` provider uses the Coding endpoint. If you need the general diff --git a/src/agents/models-config.providers.ts b/src/agents/models-config.providers.ts index a176dac8a..1b23472bf 100644 --- a/src/agents/models-config.providers.ts +++ b/src/agents/models-config.providers.ts @@ -75,6 +75,17 @@ const OLLAMA_DEFAULT_COST = { cacheWrite: 0, }; +function normalizeProviderBaseUrl(url: string): string { + return url.trim().replace(/\/+$/, ""); +} + +function resolveZaiBaseUrlEnv(env: NodeJS.ProcessEnv): string | null { + // Keep a legacy alias to mirror Z_AI_API_KEY support. + const raw = env.ZAI_BASE_URL?.trim() || env.Z_AI_BASE_URL?.trim() || ""; + if (!raw) return null; + return normalizeProviderBaseUrl(raw); +} + interface OllamaModel { name: string; modified_at: string; @@ -363,6 +374,12 @@ export async function resolveImplicitProviders(params: { agentDir: string; }): Promise { const providers: Record = {}; + const zaiBaseUrl = resolveZaiBaseUrlEnv(process.env); + if (zaiBaseUrl) { + // Override baseUrl for pi-ai built-in z.ai models without redefining models. + // This matches the "models: [] means baseUrl override only" pattern used for Copilot. + providers.zai = { baseUrl: zaiBaseUrl, models: [] }; + } const authStore = ensureAuthProfileStore(params.agentDir, { allowKeychainPrompt: false, }); diff --git a/src/agents/models-config.providers.zai-base-url.test.ts b/src/agents/models-config.providers.zai-base-url.test.ts new file mode 100644 index 000000000..169c4bf07 --- /dev/null +++ b/src/agents/models-config.providers.zai-base-url.test.ts @@ -0,0 +1,65 @@ +import fs from "node:fs/promises"; +import path from "node:path"; + +import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; +import { withTempHome as withTempHomeBase } from "../../test/helpers/temp-home.js"; +import type { MoltbotConfig } from "../config/config.js"; + +async function withTempHome(fn: (home: string) => Promise): Promise { + return withTempHomeBase(fn, { prefix: "moltbot-zai-baseurl-" }); +} + +describe("models-config: ZAI_BASE_URL", () => { + let prevHome: string | undefined; + let prevZaiBaseUrl: string | undefined; + let prevZaiLegacyBaseUrl: string | undefined; + + beforeEach(() => { + prevHome = process.env.HOME; + prevZaiBaseUrl = process.env.ZAI_BASE_URL; + prevZaiLegacyBaseUrl = process.env.Z_AI_BASE_URL; + }); + + afterEach(() => { + process.env.HOME = prevHome; + if (prevZaiBaseUrl === undefined) delete process.env.ZAI_BASE_URL; + else process.env.ZAI_BASE_URL = prevZaiBaseUrl; + if (prevZaiLegacyBaseUrl === undefined) delete process.env.Z_AI_BASE_URL; + else process.env.Z_AI_BASE_URL = prevZaiLegacyBaseUrl; + }); + + it("writes providers.zai.baseUrl from ZAI_BASE_URL (normalized)", async () => { + await withTempHome(async () => { + vi.resetModules(); + process.env.ZAI_BASE_URL = "https://proxy.example.test/v1/"; + delete process.env.Z_AI_BASE_URL; + + const { ensureMoltbotModelsJson } = await import("./models-config.js"); + const { resolveMoltbotAgentDir } = await import("./agent-paths.js"); + + await ensureMoltbotModelsJson({} as MoltbotConfig); + + const raw = await fs.readFile(path.join(resolveMoltbotAgentDir(), "models.json"), "utf8"); + const parsed = JSON.parse(raw) as { providers?: Record }; + expect(parsed.providers?.zai?.baseUrl).toBe("https://proxy.example.test/v1"); + }); + }); + + it("supports legacy Z_AI_BASE_URL when ZAI_BASE_URL is unset", async () => { + await withTempHome(async () => { + vi.resetModules(); + delete process.env.ZAI_BASE_URL; + process.env.Z_AI_BASE_URL = "http://localhost:9999/v1/"; + + const { ensureMoltbotModelsJson } = await import("./models-config.js"); + const { resolveMoltbotAgentDir } = await import("./agent-paths.js"); + + await ensureMoltbotModelsJson({} as MoltbotConfig); + + const raw = await fs.readFile(path.join(resolveMoltbotAgentDir(), "models.json"), "utf8"); + const parsed = JSON.parse(raw) as { providers?: Record }; + expect(parsed.providers?.zai?.baseUrl).toBe("http://localhost:9999/v1"); + }); + }); +}); + From 8ea582fe8e16b5dbd37cc74eb88d3625ad807407 Mon Sep 17 00:00:00 2001 From: Weixuan Xiao Date: Thu, 29 Jan 2026 16:05:18 +0100 Subject: [PATCH 2/2] Fix format --- src/agents/models-config.providers.zai-base-url.test.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/agents/models-config.providers.zai-base-url.test.ts b/src/agents/models-config.providers.zai-base-url.test.ts index 169c4bf07..b15740275 100644 --- a/src/agents/models-config.providers.zai-base-url.test.ts +++ b/src/agents/models-config.providers.zai-base-url.test.ts @@ -62,4 +62,3 @@ describe("models-config: ZAI_BASE_URL", () => { }); }); }); -