Skip to content

Commit 90551f9

Browse files
authored
Merge pull request #18 from IMvision12/zai
Add Zai models
2 parents 8c561fc + 891fd24 commit 90551f9

6 files changed

Lines changed: 121 additions & 3 deletions

File tree

README.md

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -51,9 +51,9 @@ No port forwarding. No VPN. Just message and code.
5151

5252
Connect via **6 platforms** : WhatsApp, Telegram, Discord, Slack, Microsoft Teams, and Signal.
5353

54-
### 9 AI Providers
54+
### 10 AI Providers
5555

56-
Anthropic, OpenAI, Google Gemini, Mistral, Moonshot, MiniMax, xAI Grok, HuggingFace, and OpenRouter. Instantly switch providers with `/switch` directly from your messaging app.
56+
Anthropic, OpenAI, Google Gemini, Mistral, Moonshot, MiniMax, xAI Grok, Z.ai (Zhipu), HuggingFace, and OpenRouter. Instantly switch providers with `/switch` directly from your messaging app.
5757

5858
### Hot-Switching
5959

@@ -141,7 +141,7 @@ docker run -it \
141141

142142
## 🤖 AI Providers
143143

144-
txtcode supports **9 LLM providers** for chat mode. Configure one or more during setup and hot-switch with `/switch`.
144+
txtcode supports **10 LLM providers** for chat mode. Configure one or more during setup and hot-switch with `/switch`.
145145

146146
| Provider | Example Models | Notes |
147147
| :------------------ | :----------------------------------------- | :-------------------------- |
@@ -152,6 +152,7 @@ txtcode supports **9 LLM providers** for chat mode. Configure one or more during
152152
| **Moonshot (Kimi)** | `kimi-k2.5`, `moonshot-v1-128k` | Long-context models |
153153
| **MiniMax** | `MiniMax-M2.5`, `MiniMax-M2.1` | MiniMax family |
154154
| **xAI (Grok)** | `grok-4`, `grok-3-fast` | Grok family |
155+
| **Z.ai (Zhipu)** | `glm-5`, `glm-4.7`, `glm-4.6` | GLM family |
155156
| **HuggingFace** | _Discovered at runtime_ | Inference Providers API |
156157
| **OpenRouter** | _Discovered at runtime_ | Unified API for 100+ models |
157158

src/core/router.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import { processWithMoonshot } from "../providers/moonshot";
1414
import { processWithOpenAI } from "../providers/openai";
1515
import { processWithOpenRouter } from "../providers/openrouter";
1616
import { processWithXAI } from "../providers/xai";
17+
import { processWithZAI } from "../providers/zai";
1718
import { logger } from "../shared/logger";
1819
import { IDEAdapter, ModelInfo } from "../shared/types";
1920
import { ContextManager } from "./context-manager";
@@ -146,6 +147,8 @@ export class Router {
146147
return await processWithMistral(instruction, this.apiKey, this.model);
147148
case "xai":
148149
return await processWithXAI(instruction, this.apiKey, this.model);
150+
case "zai":
151+
return await processWithZAI(instruction, this.apiKey, this.model);
149152
default:
150153
return `[ERROR] Unsupported AI provider: ${this.provider}. Run: txtcode config`;
151154
}

src/data/zai_models.json

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
{
2+
"name": "Z.ai",
3+
"models": [
4+
{
5+
"id": "glm-5",
6+
"name": "GLM-5",
7+
"description": "Flagship foundation model for agentic engineering",
8+
"recommended": true
9+
},
10+
{
11+
"id": "glm-4.7",
12+
"name": "GLM-4.7",
13+
"description": "High-performance general-purpose model"
14+
},
15+
{
16+
"id": "glm-4.6",
17+
"name": "GLM-4.6",
18+
"description": "Balanced model for chat and reasoning tasks"
19+
}
20+
]
21+
}

src/providers/zai.ts

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
import OpenAI from "openai";
2+
import { logger } from "../shared/logger";
3+
4+
const SYSTEM_PROMPT =
5+
"You are TxtCode AI — a helpful, knowledgeable coding assistant accessible via messaging. Be concise, use markdown for clarity, and suggest /code mode for deep coding work.";
6+
7+
export async function processWithZAI(
8+
instruction: string,
9+
apiKey: string,
10+
model: string,
11+
): Promise<string> {
12+
const startTime = Date.now();
13+
logger.debug(`[ZAI] Request → model=${model}, prompt=${instruction.length} chars`);
14+
15+
try {
16+
const client = new OpenAI({
17+
apiKey,
18+
baseURL: "https://api.z.ai/api/paas/v4",
19+
});
20+
21+
const completion = await client.chat.completions.create({
22+
model,
23+
max_tokens: 4096,
24+
messages: [
25+
{ role: "system", content: SYSTEM_PROMPT },
26+
{ role: "user", content: instruction },
27+
],
28+
});
29+
30+
const choice = completion.choices[0];
31+
32+
logger.debug(
33+
`[ZAI] Done in ${Date.now() - startTime}ms, ` +
34+
`tokens=${completion.usage?.prompt_tokens ?? "?"}in/${completion.usage?.completion_tokens ?? "?"}out`,
35+
);
36+
37+
return choice.message.content || "No response from Z.ai";
38+
} catch (error: unknown) {
39+
logger.error(`[ZAI] API error after ${Date.now() - startTime}ms`, error);
40+
throw new Error(`Z.ai API error: ${error instanceof Error ? error.message : "Unknown error"}`, {
41+
cause: error,
42+
});
43+
}
44+
}

test/unit/router.test.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -181,6 +181,12 @@ describe("Router", () => {
181181
expect(result).toContain("[ERROR]");
182182
expect(result).toContain("Unsupported");
183183
});
184+
185+
it("routes to zai provider without throwing", async () => {
186+
router.updateProvider("zai", "test-key", "glm-5");
187+
expect(router.getProviderName()).toBe("zai");
188+
expect(router.getCurrentModel()).toBe("glm-5");
189+
});
184190
});
185191

186192
describe("abortCurrentCommand", () => {

test/unit/zai-provider.test.ts

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
import { describe, it, expect, beforeEach } from "vitest";
2+
import { loadModelsCatalog, clearCatalogCache } from "../../src/utils/models-catalog-loader";
3+
4+
describe("Z.ai provider", () => {
5+
beforeEach(() => {
6+
clearCatalogCache();
7+
});
8+
9+
describe("model catalog", () => {
10+
it("is discovered by the catalog loader", () => {
11+
const catalog = loadModelsCatalog();
12+
expect(catalog.providers.zai).toBeDefined();
13+
});
14+
15+
it("has correct provider name", () => {
16+
const catalog = loadModelsCatalog();
17+
expect(catalog.providers.zai.name).toBe("Z.ai");
18+
});
19+
20+
it("includes all expected models", () => {
21+
const catalog = loadModelsCatalog();
22+
const ids = catalog.providers.zai.models.map((m) => m.id);
23+
expect(ids).toContain("glm-5");
24+
expect(ids).toContain("glm-4.7");
25+
expect(ids).toContain("glm-4.6");
26+
});
27+
28+
it("has glm-5 marked as recommended", () => {
29+
const catalog = loadModelsCatalog();
30+
const glm5 = catalog.providers.zai.models.find((m) => m.id === "glm-5");
31+
expect(glm5).toBeDefined();
32+
expect(glm5!.recommended).toBe(true);
33+
});
34+
35+
it("every model has a description", () => {
36+
const catalog = loadModelsCatalog();
37+
for (const model of catalog.providers.zai.models) {
38+
expect(model.description).toBeDefined();
39+
expect(model.description.length).toBeGreaterThan(0);
40+
}
41+
});
42+
});
43+
});

0 commit comments

Comments
 (0)