feat(zai): auto-detect endpoint + default glm-5 (#14786)

* feat(zai): auto-detect endpoint + default glm-5

* test: fix Z.AI default endpoint expectation (#14786)

* test: bump embedded runner beforeAll timeout

* chore: update changelog for Z.AI GLM-5 autodetect (#14786)

* chore: resolve changelog merge conflict with main (#14786)

* chore: append changelog note for #14786 without merge conflict

* chore: sync changelog with main to resolve merge conflict
This commit is contained in:
Peter Steinberger
2026-02-12 19:16:04 +01:00
committed by GitHub
parent 2b5df1dfea
commit 5e7842a41d
15 changed files with 406 additions and 66 deletions

View File

@@ -104,7 +104,7 @@ beforeAll(async () => {
workspaceDir = path.join(tempRoot, "workspace");
await fs.mkdir(agentDir, { recursive: true });
await fs.mkdir(workspaceDir, { recursive: true });
}, 20_000);
}, 60_000);
afterAll(async () => {
if (!tempRoot) {

View File

@@ -242,6 +242,41 @@ describe("resolveModel", () => {
});
});
it("builds a zai forward-compat fallback for glm-5", () => {
const templateModel = {
id: "glm-4.7",
name: "GLM-4.7",
provider: "zai",
api: "openai-completions",
baseUrl: "https://api.z.ai/api/paas/v4",
reasoning: true,
input: ["text"] as const,
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
contextWindow: 200000,
maxTokens: 131072,
};
vi.mocked(discoverModels).mockReturnValue({
find: vi.fn((provider: string, modelId: string) => {
if (provider === "zai" && modelId === "glm-4.7") {
return templateModel;
}
return null;
}),
} as unknown as ReturnType<typeof discoverModels>);
const result = resolveModel("zai", "glm-5", "/tmp/agent");
expect(result.error).toBeUndefined();
expect(result.model).toMatchObject({
provider: "zai",
id: "glm-5",
api: "openai-completions",
baseUrl: "https://api.z.ai/api/paas/v4",
reasoning: true,
});
});
it("keeps unknown-model errors for non-gpt-5 openai-codex ids", () => {
const result = resolveModel("openai-codex", "gpt-4.1-mini", "/tmp/agent");
expect(result.model).toBeUndefined();

View File

@@ -114,6 +114,51 @@ function resolveAnthropicOpus46ForwardCompatModel(
return undefined;
}
// Z.ai's GLM-5 may not be present in pi-ai's built-in model catalog yet.
// When a user configures zai/glm-5 without a models.json entry, clone glm-4.7 as a forward-compat fallback.
const ZAI_GLM5_MODEL_ID = "glm-5";
const ZAI_GLM5_TEMPLATE_MODEL_IDS = ["glm-4.7"] as const;
function resolveZaiGlm5ForwardCompatModel(
provider: string,
modelId: string,
modelRegistry: ModelRegistry,
): Model<Api> | undefined {
if (normalizeProviderId(provider) !== "zai") {
return undefined;
}
const trimmed = modelId.trim();
const lower = trimmed.toLowerCase();
if (lower !== ZAI_GLM5_MODEL_ID && !lower.startsWith(`${ZAI_GLM5_MODEL_ID}-`)) {
return undefined;
}
for (const templateId of ZAI_GLM5_TEMPLATE_MODEL_IDS) {
const template = modelRegistry.find("zai", templateId) as Model<Api> | null;
if (!template) {
continue;
}
return normalizeModelCompat({
...template,
id: trimmed,
name: trimmed,
reasoning: true,
} as Model<Api>);
}
return normalizeModelCompat({
id: trimmed,
name: trimmed,
api: "openai-completions",
provider: "zai",
reasoning: true,
input: ["text"],
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
contextWindow: DEFAULT_CONTEXT_TOKENS,
maxTokens: DEFAULT_CONTEXT_TOKENS,
} as Model<Api>);
}
// google-antigravity's model catalog in pi-ai can lag behind the actual platform.
// When a google-antigravity model ID contains "opus-4-6" (or "opus-4.6") but isn't
// in the registry yet, clone the opus-4-5 template so the correct api
@@ -242,6 +287,10 @@ export function resolveModel(
if (antigravityForwardCompat) {
return { model: antigravityForwardCompat, authStorage, modelRegistry };
}
const zaiForwardCompat = resolveZaiGlm5ForwardCompatModel(provider, modelId, modelRegistry);
if (zaiForwardCompat) {
return { model: zaiForwardCompat, authStorage, modelRegistry };
}
const providerCfg = providers[provider];
if (providerCfg || modelId.startsWith("mock-")) {
const fallbackModel: Model<Api> = normalizeModelCompat({