Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/add-glm-5-2-support.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"zoo-code": minor
---

Add GLM-5.2 support with High/Max `reasoning_effort` tiers. The default effort is High (deep reasoning stays opt-in), Max is selected only when the user explicitly picks it, and the parameter is omitted entirely when reasoning is disabled.
15 changes: 12 additions & 3 deletions packages/types/src/model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ export type ReasoningEffortWithMinimal = z.infer<typeof reasoningEffortWithMinim
* Extended Reasoning Effort (includes "none" and "minimal")
* Note: "disable" is a UI/control value, not a value sent as effort
*/
export const reasoningEffortsExtended = ["none", "minimal", "low", "medium", "high", "xhigh"] as const
export const reasoningEffortsExtended = ["none", "minimal", "low", "medium", "high", "xhigh", "max"] as const

export const reasoningEffortExtendedSchema = z.enum(reasoningEffortsExtended)

Expand All @@ -32,7 +32,16 @@ export type ReasoningEffortExtended = z.infer<typeof reasoningEffortExtendedSche
/**
* Reasoning Effort user setting (includes "disable")
*/
export const reasoningEffortSettingValues = ["disable", "none", "minimal", "low", "medium", "high", "xhigh"] as const
export const reasoningEffortSettingValues = [
"disable",
"none",
"minimal",
"low",
"medium",
"high",
"xhigh",
"max",
] as const
export const reasoningEffortSettingSchema = z.enum(reasoningEffortSettingValues)

/**
Expand Down Expand Up @@ -93,7 +102,7 @@ export const modelInfoSchema = z.object({
defaultTemperature: z.number().optional(),
requiredReasoningBudget: z.boolean().optional(),
supportsReasoningEffort: z
.union([z.boolean(), z.array(z.enum(["disable", "none", "minimal", "low", "medium", "high", "xhigh"]))])
.union([z.boolean(), z.array(z.enum(["disable", "none", "minimal", "low", "medium", "high", "xhigh", "max"]))])
.optional(),
requiredReasoningEffort: z.boolean().optional(),
preserveReasoning: z.boolean().optional(),
Expand Down
32 changes: 32 additions & 0 deletions packages/types/src/providers/zai.ts
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,22 @@ export const internationalZAiModels = {
description:
"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.",
},
"glm-5.2": {
maxTokens: 131_072,
contextWindow: 1_000_000,
supportsImages: false,
supportsPromptCache: true,
supportsMaxTokens: true,
supportsReasoningEffort: ["disable", "high", "max"],
reasoningEffort: "high",
preserveReasoning: true,
inputPrice: 1.4,
outputPrice: 4.4,
Comment thread
MobCode100 marked this conversation as resolved.
cacheWritesPrice: 0,
cacheReadsPrice: 0.26,
description:
"GLM-5.2 is Zhipu's flagship model with a 1M context window, 128k max output, and dual thinking-effort modes (High/Max). It delivers top-tier long-context reasoning, coding, and agentic performance for extended engineering sessions.",
},
"glm-5-turbo": {
maxTokens: 131_072,
contextWindow: 202_752,
Expand Down Expand Up @@ -361,6 +377,22 @@ export const mainlandZAiModels = {
description:
"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.",
},
"glm-5.2": {
maxTokens: 131_072,
contextWindow: 1_000_000,
supportsImages: false,
supportsPromptCache: true,
supportsMaxTokens: true,
supportsReasoningEffort: ["disable", "high", "max"],
reasoningEffort: "high",
preserveReasoning: true,
inputPrice: 0.68,
outputPrice: 2.28,
cacheWritesPrice: 0,
cacheReadsPrice: 0.13,
description:
"GLM-5.2 is Zhipu's flagship model with a 1M context window, 128k max output, and dual thinking-effort modes (High/Max). It delivers top-tier long-context reasoning, coding, and agentic performance for extended engineering sessions.",
},
"glm-5-turbo": {
maxTokens: 131_072,
contextWindow: 202_752,
Expand Down
128 changes: 128 additions & 0 deletions src/api/providers/__tests__/zai.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,27 @@ describe("ZAiHandler", () => {
expect(model.info.supportsImages).toBe(false)
})

it("should return GLM-5.2 international model with High/Max effort tiers and 1M context", () => {
const testModelId: InternationalZAiModelId = "glm-5.2"
const handlerWithModel = new ZAiHandler({
apiModelId: testModelId,
zaiApiKey: "test-zai-api-key",
zaiApiLine: "international_coding",
})
const model = handlerWithModel.getModel()
expect(model.id).toBe(testModelId)
expect(model.info).toEqual(internationalZAiModels[testModelId])
expect(model.info.contextWindow).toBe(1_000_000)
expect(model.info.maxTokens).toBe(131_072)
expect(model.info.supportsReasoningEffort).toEqual(["disable", "high", "max"])
expect(model.info.reasoningEffort).toBe("high")
expect(model.info.preserveReasoning).toBe(true)
expect(model.info.supportsMaxTokens).toBe(true)
expect(model.info.inputPrice).toBe(1.4)
expect(model.info.outputPrice).toBe(4.4)
expect(model.info.cacheReadsPrice).toBe(0.26)
})

it("should return GLM-5-Turbo international model with thinking support", () => {
const testModelId: InternationalZAiModelId = "glm-5-turbo"
const handlerWithModel = new ZAiHandler({
Expand Down Expand Up @@ -231,6 +252,27 @@ describe("ZAiHandler", () => {
expect(model.info.supportsImages).toBe(false)
})

it("should return GLM-5.2 China model with High/Max effort tiers and 1M context", () => {
const testModelId: MainlandZAiModelId = "glm-5.2"
const handlerWithModel = new ZAiHandler({
apiModelId: testModelId,
zaiApiKey: "test-zai-api-key",
zaiApiLine: "china_coding",
})
const model = handlerWithModel.getModel()
expect(model.id).toBe(testModelId)
expect(model.info).toEqual(mainlandZAiModels[testModelId])
expect(model.info.contextWindow).toBe(1_000_000)
expect(model.info.maxTokens).toBe(131_072)
expect(model.info.supportsReasoningEffort).toEqual(["disable", "high", "max"])
expect(model.info.reasoningEffort).toBe("high")
expect(model.info.preserveReasoning).toBe(true)
expect(model.info.supportsMaxTokens).toBe(true)
expect(model.info.inputPrice).toBe(0.68)
expect(model.info.outputPrice).toBe(2.28)
expect(model.info.cacheReadsPrice).toBe(0.13)
})

it("should return GLM-4.7 China model with thinking support", () => {
const testModelId: MainlandZAiModelId = "glm-4.7"
const handlerWithModel = new ZAiHandler({
Expand Down Expand Up @@ -573,6 +615,92 @@ describe("ZAiHandler", () => {
)
})

it("should send reasoning_effort:high by default for GLM-5.2 (model default)", async () => {
const handlerWithModel = new ZAiHandler({
apiModelId: "glm-5.2",
zaiApiKey: "test-zai-api-key",
zaiApiLine: "international_coding",
// No reasoningEffort setting - should use model default (high)
})

mockCreate.mockImplementationOnce(() => {
return {
[Symbol.asyncIterator]: () => ({
async next() {
return { done: true }
},
}),
}
})

const messageGenerator = handlerWithModel.createMessage("system prompt", [])
await messageGenerator.next()

expect(mockCreate).toHaveBeenCalledWith(
expect.objectContaining({
model: "glm-5.2",
thinking: { type: "enabled" },
reasoning_effort: "high",
}),
)
})

it("should send reasoning_effort:max for GLM-5.2 when reasoningEffort is set to max", async () => {
const handlerWithModel = new ZAiHandler({
apiModelId: "glm-5.2",
zaiApiKey: "test-zai-api-key",
zaiApiLine: "international_coding",
reasoningEffort: "max",
})

mockCreate.mockImplementationOnce(() => {
return {
[Symbol.asyncIterator]: () => ({
async next() {
return { done: true }
},
}),
}
})

const messageGenerator = handlerWithModel.createMessage("system prompt", [])
await messageGenerator.next()

expect(mockCreate).toHaveBeenCalledWith(
expect.objectContaining({
model: "glm-5.2",
thinking: { type: "enabled" },
reasoning_effort: "max",
}),
)
})

it("should omit reasoning_effort for GLM-5.2 when reasoningEffort is set to disable", async () => {
const handlerWithModel = new ZAiHandler({
apiModelId: "glm-5.2",
zaiApiKey: "test-zai-api-key",
zaiApiLine: "international_coding",
reasoningEffort: "disable",
})

mockCreate.mockImplementationOnce(() => {
return {
[Symbol.asyncIterator]: () => ({
async next() {
return { done: true }
},
}),
}
})

const messageGenerator = handlerWithModel.createMessage("system prompt", [])
await messageGenerator.next()

const callArgs = mockCreate.mock.calls[0][0]
expect(callArgs.thinking).toEqual({ type: "disabled" })
expect(callArgs.reasoning_effort).toBeUndefined()
})

it("should disable thinking for GLM-4.7 when reasoningEffort is set to disable", async () => {
const handlerWithModel = new ZAiHandler({
apiModelId: "glm-4.7",
Expand Down
18 changes: 15 additions & 3 deletions src/api/providers/zai.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,13 @@ import { convertToZAiFormat } from "../transform/zai-format"
import type { ApiHandlerCreateMessageMetadata } from "../index"
import { BaseOpenAiCompatibleProvider } from "./base-openai-compatible-provider"

// Custom interface for Z.ai params to support thinking mode
type ZAiChatCompletionParams = OpenAI.Chat.ChatCompletionCreateParamsStreaming & {
// Custom interface for Z.ai params to support thinking mode and reasoning effort tiers.
// Z.ai accepts the standard `reasoning_effort` ladder (none/minimal/low/medium/high/xhigh/max)
// alongside the GLM-specific `thinking` toggle. Omit the OpenAI-typed `reasoning_effort` so we
// can widen it to include provider-specific values such as "max".
type ZAiChatCompletionParams = Omit<OpenAI.Chat.ChatCompletionCreateParamsStreaming, "reasoning_effort"> & {
thinking?: { type: "enabled" | "disabled" }
reasoning_effort?: "none" | "minimal" | "low" | "medium" | "high" | "xhigh" | "max"
}

export class ZAiHandler extends BaseOpenAiCompatibleProvider<string> {
Expand Down Expand Up @@ -79,6 +83,11 @@ export class ZAiHandler extends BaseOpenAiCompatibleProvider<string> {
) {
const { id: model, info } = this.getModel()

// Resolve the reasoning-effort tier (e.g. "high" | "max" for GLM-5.2) from the user
// setting, falling back to the model's default. Omitted when reasoning is disabled.
const effort = useReasoning ? (this.options.reasoningEffort ?? info.reasoningEffort) : undefined
const reasoningEffort = effort && effort !== "disable" ? effort : undefined
Comment thread
MobCode100 marked this conversation as resolved.
Outdated

const max_tokens =
this.options.modelMaxTokens ||
(getModelMaxOutputTokens({
Expand All @@ -103,11 +112,14 @@ export class ZAiHandler extends BaseOpenAiCompatibleProvider<string> {
stream_options: { include_usage: true },
// Thinking is ON by default for these models, so explicitly disable it when needed.
thinking: useReasoning ? { type: "enabled" } : { type: "disabled" },
reasoning_effort: reasoningEffort,
tools: this.convertToolsForOpenAI(metadata?.tools),
tool_choice: metadata?.tool_choice,
parallel_tool_calls: metadata?.parallelToolCalls ?? true,
}

return this.client.chat.completions.create(params)
return this.client.chat.completions.create(
params as OpenAI.Chat.Completions.ChatCompletionCreateParamsStreaming,
)
Comment thread
MobCode100 marked this conversation as resolved.
Outdated
}
}
1 change: 1 addition & 0 deletions webview-ui/src/i18n/locales/ca/settings.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions webview-ui/src/i18n/locales/de/settings.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion webview-ui/src/i18n/locales/en/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -620,7 +620,8 @@
"low": "Low",
"medium": "Medium",
"high": "High",
"xhigh": "Extra High"
"xhigh": "Extra High",
"max": "Max"
},
"verbosity": {
"label": "Output Verbosity",
Expand Down
1 change: 1 addition & 0 deletions webview-ui/src/i18n/locales/es/settings.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions webview-ui/src/i18n/locales/fr/settings.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions webview-ui/src/i18n/locales/hi/settings.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions webview-ui/src/i18n/locales/id/settings.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions webview-ui/src/i18n/locales/it/settings.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions webview-ui/src/i18n/locales/ja/settings.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions webview-ui/src/i18n/locales/ko/settings.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions webview-ui/src/i18n/locales/nl/settings.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions webview-ui/src/i18n/locales/pl/settings.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions webview-ui/src/i18n/locales/pt-BR/settings.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions webview-ui/src/i18n/locales/ru/settings.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading