Skip to content

Commit 32cdf80

Browse files
roomote[bot]taltasedelauna
authored
[Fix] GLM-5-Turbo is missing from the Z.ai provider (#51)
* fix: add glm-5-turbo to zai provider * refactor(zai): bumping up maxTokens for 5.1 turbo - adding e2e test --------- Co-authored-by: Toray Altas <6816042+taltas@users.noreply.github.com> Co-authored-by: Elliott de Launay <edelauna@gmail.com>
1 parent 1aa39e5 commit 32cdf80

4 files changed

Lines changed: 170 additions & 5 deletions

File tree

apps/vscode-e2e/src/suite/providers/zai.test.ts

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -153,7 +153,10 @@ suite("Z.ai GLM provider", function () {
153153

154154
suiteSetup(async () => {
155155
restoreFetch = installZAiFetchInterceptor(
156-
[{ match: "zai-glm-e2e:", result: "4" }],
156+
[
157+
{ match: "zai-glm-e2e:", result: "4" },
158+
{ match: "zai-glm-5-turbo-e2e:", result: "4" },
159+
],
157160
requestCapture,
158161
!!ZAI_API_KEY,
159162
)
@@ -211,4 +214,43 @@ suite("Z.ai GLM provider", function () {
211214
`max_tokens should be the documented glm-5.1 limit (131_072) but was ${requestCapture.maxTokens}`,
212215
)
213216
})
217+
218+
test("Should complete a task end-to-end using glm-5-turbo via Z.ai provider", async () => {
219+
await globalThis.api.setConfiguration({
220+
apiProvider: "zai" as const,
221+
zaiApiKey: ZAI_API_KEY ?? "mock-key",
222+
zaiApiLine: "international_api" as const,
223+
apiModelId: "glm-5-turbo",
224+
})
225+
226+
const api = globalThis.api
227+
const messages: ClineMessage[] = []
228+
229+
api.on(RooCodeEventName.Message, ({ message }) => {
230+
if (message.type === "say" && message.partial === false) {
231+
messages.push(message)
232+
}
233+
})
234+
235+
const taskId = await api.startNewTask({
236+
configuration: { mode: "ask", alwaysAllowModeSwitch: true, autoApprovalEnabled: true },
237+
text: "zai-glm-5-turbo-e2e: what is 2+2? Reply with only the number.",
238+
})
239+
240+
await waitUntilCompleted({ api, taskId })
241+
242+
const completionMessage = messages.find(
243+
({ say, text }) => (say === "completion_result" || say === "text") && text?.trim() === "4",
244+
)
245+
246+
assert.ok(completionMessage, "Task should complete with the expected Z.ai GLM-5-Turbo response")
247+
248+
// Verify max_tokens is the model's documented limit (131_072), not the 20%-of-context
249+
// heuristic cap (40_000) that guards against inaccurate OpenRouter dynamic metadata.
250+
assert.strictEqual(
251+
requestCapture.maxTokens,
252+
131_072,
253+
`max_tokens should be the documented glm-5-turbo limit (131_072) but was ${requestCapture.maxTokens}`,
254+
)
255+
})
214256
})

packages/types/src/providers/zai.ts

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { ZaiApiLine } from "../provider-settings.js"
66
// https://docs.z.ai/guides/llm/glm-4.5
77
// https://docs.z.ai/guides/llm/glm-4.6
88
// https://docs.z.ai/guides/llm/glm-5.1
9+
// https://docs.z.ai/guides/llm/glm-5-turbo
910
// https://docs.z.ai/guides/overview/pricing
1011
// https://bigmodel.cn/pricing
1112

@@ -151,6 +152,21 @@ export const internationalZAiModels = {
151152
description:
152153
"GLM-5.1 is Zhipu's most capable model with a 200k context window, 128k max output, and built-in thinking capabilities. It delivers top-tier reasoning, coding, and agentic performance.",
153154
},
155+
"glm-5-turbo": {
156+
maxTokens: 131_072,
157+
contextWindow: 202_752,
158+
supportsImages: false,
159+
supportsPromptCache: true,
160+
supportsReasoningEffort: ["disable", "medium"],
161+
reasoningEffort: "medium",
162+
preserveReasoning: true,
163+
inputPrice: 1.2,
164+
outputPrice: 4.0,
165+
cacheWritesPrice: 0,
166+
cacheReadsPrice: 0.24,
167+
description:
168+
"GLM-5-Turbo is Zhipu's OpenClaw-optimized model with built-in thinking capabilities. It improves tool use, instruction following, and long-running agent workflows while keeping fast response times.",
169+
},
154170
"glm-4.7-flash": {
155171
maxTokens: 16_384,
156172
contextWindow: 200_000,
@@ -342,6 +358,21 @@ export const mainlandZAiModels = {
342358
description:
343359
"GLM-5.1 is Zhipu's most capable model with a 200k context window, 128k max output, and built-in thinking capabilities. It delivers top-tier reasoning, coding, and agentic performance.",
344360
},
361+
"glm-5-turbo": {
362+
maxTokens: 131_072,
363+
contextWindow: 202_752,
364+
supportsImages: false,
365+
supportsPromptCache: true,
366+
supportsReasoningEffort: ["disable", "medium"],
367+
reasoningEffort: "medium",
368+
preserveReasoning: true,
369+
inputPrice: 0.35,
370+
outputPrice: 1.43,
371+
cacheWritesPrice: 0,
372+
cacheReadsPrice: 0.07,
373+
description:
374+
"GLM-5-Turbo is Zhipu's OpenClaw-optimized model with built-in thinking capabilities. It improves tool use, instruction following, and long-running agent workflows while keeping fast response times.",
375+
},
345376
"glm-4.7-flash": {
346377
maxTokens: 16_384,
347378
contextWindow: 204_800,

src/api/providers/__tests__/zai.spec.ts

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,23 @@ describe("ZAiHandler", () => {
116116
expect(model.info.supportsImages).toBe(false)
117117
})
118118

119+
it("should return GLM-5-Turbo international model with thinking support", () => {
120+
const testModelId: InternationalZAiModelId = "glm-5-turbo"
121+
const handlerWithModel = new ZAiHandler({
122+
apiModelId: testModelId,
123+
zaiApiKey: "test-zai-api-key",
124+
zaiApiLine: "international_coding",
125+
})
126+
const model = handlerWithModel.getModel()
127+
expect(model.id).toBe(testModelId)
128+
expect(model.info).toEqual(internationalZAiModels[testModelId])
129+
expect(model.info.contextWindow).toBe(202_752)
130+
expect(model.info.maxTokens).toBe(131_072)
131+
expect(model.info.supportsReasoningEffort).toEqual(["disable", "medium"])
132+
expect(model.info.reasoningEffort).toBe("medium")
133+
expect(model.info.preserveReasoning).toBe(true)
134+
})
135+
119136
it("should return GLM-4.5v international model with vision support", () => {
120137
const testModelId: InternationalZAiModelId = "glm-4.5v"
121138
const handlerWithModel = new ZAiHandler({
@@ -229,6 +246,23 @@ describe("ZAiHandler", () => {
229246
expect(model.info.reasoningEffort).toBe("medium")
230247
expect(model.info.preserveReasoning).toBe(true)
231248
})
249+
250+
it("should return GLM-5-Turbo China model with thinking support", () => {
251+
const testModelId: MainlandZAiModelId = "glm-5-turbo"
252+
const handlerWithModel = new ZAiHandler({
253+
apiModelId: testModelId,
254+
zaiApiKey: "test-zai-api-key",
255+
zaiApiLine: "china_coding",
256+
})
257+
const model = handlerWithModel.getModel()
258+
expect(model.id).toBe(testModelId)
259+
expect(model.info).toEqual(mainlandZAiModels[testModelId])
260+
expect(model.info.contextWindow).toBe(202_752)
261+
expect(model.info.maxTokens).toBe(131_072)
262+
expect(model.info.supportsReasoningEffort).toEqual(["disable", "medium"])
263+
expect(model.info.reasoningEffort).toBe("medium")
264+
expect(model.info.preserveReasoning).toBe(true)
265+
})
232266
})
233267

234268
describe("International API", () => {
@@ -557,5 +591,63 @@ describe("ZAiHandler", () => {
557591
const callArgs = mockCreate.mock.calls[0][0]
558592
expect(callArgs.thinking).toBeUndefined()
559593
})
594+
595+
it("should enable thinking by default for GLM-5-Turbo", async () => {
596+
const handlerWithModel = new ZAiHandler({
597+
apiModelId: "glm-5-turbo",
598+
zaiApiKey: "test-zai-api-key",
599+
zaiApiLine: "international_coding",
600+
})
601+
602+
mockCreate.mockImplementationOnce(() => {
603+
return {
604+
[Symbol.asyncIterator]: () => ({
605+
async next() {
606+
return { done: true }
607+
},
608+
}),
609+
}
610+
})
611+
612+
const messageGenerator = handlerWithModel.createMessage("system prompt", [])
613+
await messageGenerator.next()
614+
615+
expect(mockCreate).toHaveBeenCalledWith(
616+
expect.objectContaining({
617+
model: "glm-5-turbo",
618+
thinking: { type: "enabled" },
619+
}),
620+
)
621+
})
622+
623+
it("should disable thinking for GLM-5-Turbo when reasoningEffort is set to disable", async () => {
624+
const handlerWithModel = new ZAiHandler({
625+
apiModelId: "glm-5-turbo",
626+
zaiApiKey: "test-zai-api-key",
627+
zaiApiLine: "international_coding",
628+
enableReasoningEffort: true,
629+
reasoningEffort: "disable",
630+
})
631+
632+
mockCreate.mockImplementationOnce(() => {
633+
return {
634+
[Symbol.asyncIterator]: () => ({
635+
async next() {
636+
return { done: true }
637+
},
638+
}),
639+
}
640+
})
641+
642+
const messageGenerator = handlerWithModel.createMessage("system prompt", [])
643+
await messageGenerator.next()
644+
645+
expect(mockCreate).toHaveBeenCalledWith(
646+
expect.objectContaining({
647+
model: "glm-5-turbo",
648+
thinking: { type: "disabled" },
649+
}),
650+
)
651+
})
560652
})
561653
})

src/api/providers/zai.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -40,8 +40,8 @@ export class ZAiHandler extends BaseOpenAiCompatibleProvider<string> {
4040
}
4141

4242
/**
43-
* Override createStream to handle GLM-4.7's thinking mode.
44-
* GLM-4.7 has thinking enabled by default in the API, so we need to
43+
* Override createStream to handle GLM thinking-capable models.
44+
* These models have thinking enabled by default in the API, so we need to
4545
* explicitly send { type: "disabled" } when the user turns off reasoning.
4646
*/
4747
protected override createStream(
@@ -69,7 +69,7 @@ export class ZAiHandler extends BaseOpenAiCompatibleProvider<string> {
6969
}
7070

7171
/**
72-
* Creates a stream with explicit thinking control for GLM-4.7
72+
* Creates a stream with explicit thinking control for GLM thinking-capable models.
7373
*/
7474
private createStreamWithThinking(
7575
systemPrompt: string,
@@ -97,7 +97,7 @@ export class ZAiHandler extends BaseOpenAiCompatibleProvider<string> {
9797
messages: [{ role: "system", content: systemPrompt }, ...convertedMessages],
9898
stream: true,
9999
stream_options: { include_usage: true },
100-
// For GLM-4.7: thinking is ON by default, so we explicitly disable when needed
100+
// Thinking is ON by default for these models, so explicitly disable it when needed.
101101
thinking: useReasoning ? { type: "enabled" } : { type: "disabled" },
102102
tools: this.convertToolsForOpenAI(metadata?.tools),
103103
tool_choice: metadata?.tool_choice,

0 commit comments

Comments
 (0)