Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
2 changes: 1 addition & 1 deletion packages/types/src/__tests__/lite-llm.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ describe("LiteLLM preserveReasoning model detection", () => {

it("matches case-insensitively", () => {
expect(isLiteLLMPreserveReasoningModel("MiniMax-M2.7-Highspeed")).toBe(true)
expect(isLiteLLMPreserveReasoningModel("GLM-5.2")).toBe(true)
expect(isLiteLLMPreserveReasoningModel("GLM-5.3")).toBe(true)
})

it("does not match model ids that merely contain a known family as a substring", () => {
Expand Down
17 changes: 17 additions & 0 deletions packages/types/src/__tests__/opencode-go.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ describe("opencode-go registry", () => {
"glm-5",
"glm-5.1",
"glm-5.2",
"glm-5.3",
"kimi-k3",
"kimi-k2.5",
"kimi-k2.6",
Expand Down Expand Up @@ -78,6 +79,22 @@ describe("opencode-go registry", () => {
expect(info?.outputPrice).toBe(15.0)
expect(info?.cacheReadsPrice).toBe(0.3)
})

it("glm-5.3 exposes its native context, pricing, and always-on reasoning levels", () => {
const info = getOpencodeGoModelInfo("glm-5.3")
expect(info).toBeDefined()
expect(info?.maxTokens).toBe(131_072)
expect(info?.contextWindow).toBe(1_000_000)
expect(info?.supportsImages).toBe(false)
expect(info?.supportsPromptCache).toBe(true)
expect(info?.supportsMaxTokens).toBe(true)
expect(info?.supportsReasoningEffort).toEqual(["low", "high", "max"])
expect(info?.reasoningEffort).toBe("max")
expect(info?.preserveReasoning).toBe(true)
expect(info?.inputPrice).toBe(1.4)
expect(info?.outputPrice).toBe(4.4)
expect(info?.cacheReadsPrice).toBe(0.26)
})
})

describe("OPENCODE_GO_ANTHROPIC_FORMAT_MODELS", () => {
Expand Down
2 changes: 1 addition & 1 deletion packages/types/src/__tests__/provider-settings.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,7 @@ describe("getApiProtocol", () => {
})

it("should return 'openai' for opencode-go OpenAI-format models (GLM/DeepSeek/etc.)", () => {
expect(getApiProtocol(providerIdentifiers.opencodeGo, "glm-5.2")).toBe(OPENAI_API_PROTOCOL)
expect(getApiProtocol(providerIdentifiers.opencodeGo, "glm-5.3")).toBe(OPENAI_API_PROTOCOL)
expect(getApiProtocol(providerIdentifiers.opencodeGo, "deepseek-v4-pro")).toBe(OPENAI_API_PROTOCOL)
expect(getApiProtocol(providerIdentifiers.opencodeGo, "kimi-k2.5")).toBe(OPENAI_API_PROTOCOL)
expect(getApiProtocol(providerIdentifiers.opencodeGo, "mimo-v2.5")).toBe(OPENAI_API_PROTOCOL)
Expand Down
1 change: 1 addition & 0 deletions packages/types/src/providers/lite-llm.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ export const LITELLM_PRESERVE_REASONING_MODEL_IDS = [
"glm-5",
"glm-5.1",
"glm-5.2",
"glm-5.3",
"glm-5-turbo",

// bedrock.ts, minimax.ts, opencode-go.ts
Expand Down
15 changes: 15 additions & 0 deletions packages/types/src/providers/opencode-go.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,21 @@ export const opencodeGoModels: Record<string, ModelInfo> = {
description:
"GLM-5.1 is Zhipu's most capable model with a 200k context window, 128k max output, and built-in thinking capabilities. Available via the Opencode Go plan.",
},
"glm-5.3": {
maxTokens: 131_072,
contextWindow: 1_000_000,
supportsImages: false,
supportsPromptCache: true,
supportsMaxTokens: true,
supportsReasoningEffort: ["low", "high", "max"],
reasoningEffort: "max",
preserveReasoning: true,
inputPrice: 1.4,
outputPrice: 4.4,
cacheReadsPrice: 0.26,
description:
"GLM-5.3 is Zhipu's flagship coding and agent model with a 1M context window, 128k max output, and always-on reasoning with configurable effort (Low/High/Max). Available via the Opencode Go plan.",
},
"glm-5.2": {
maxTokens: 131_072,
contextWindow: 1_000_000,
Expand Down
44 changes: 44 additions & 0 deletions packages/types/src/providers/zai.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { ZaiApiLine } from "../provider-settings.js"
// https://docs.z.ai/guides/llm/glm-4-32b-0414-128k
// https://docs.z.ai/guides/llm/glm-4.5
// https://docs.z.ai/guides/llm/glm-4.6
// https://docs.z.ai/guides/llm/glm-5.3
// https://docs.z.ai/guides/llm/glm-5.1
// https://docs.z.ai/guides/llm/glm-5-turbo
// https://docs.z.ai/guides/overview/pricing
Expand Down Expand Up @@ -473,6 +474,42 @@ export const mainlandZAiModels = {
},
} as const satisfies Record<string, ModelInfo>

const glm53CodingPlanModelInfo = {
maxTokens: 131_072,
contextWindow: 1_000_000,
supportsImages: false,
supportsPromptCache: true,
supportsMaxTokens: true,
supportsReasoningEffort: ["low", "high", "max"],
requiredReasoningEffort: true,
reasoningEffort: "max",
preserveReasoning: true,
description:
"GLM-5.3 is Zhipu's flagship coding and agent model with a 1M context window, 128k max output, and always-on reasoning with configurable effort (Low/High/Max). Available to GLM Coding Plan users.",
} as const satisfies ModelInfo

export const internationalZAiCodingPlanOnlyModels = {
"glm-5.3": {
...glm53CodingPlanModelInfo,
// GLM-5.3 API pricing is not published yet; use GLM-5.2 pricing provisionally.
inputPrice: 1.4,
outputPrice: 4.4,
cacheWritesPrice: 0,
cacheReadsPrice: 0.26,
},
} as const satisfies Record<string, ModelInfo>

export const mainlandZAiCodingPlanOnlyModels = {
"glm-5.3": {
...glm53CodingPlanModelInfo,
// GLM-5.3 API pricing is not published yet; use GLM-5.2 pricing provisionally.
inputPrice: 0.68,
outputPrice: 2.28,
cacheWritesPrice: 0,
cacheReadsPrice: 0.13,
},
} as const satisfies Record<string, ModelInfo>

export const ZAI_DEFAULT_TEMPERATURE = 0.6

export const zaiApiLineConfigs = {
Expand All @@ -497,3 +534,10 @@ export const zaiApiLineConfigs = {
isChina: true,
},
} satisfies Record<ZaiApiLine, { name: string; baseUrl: string; isChina: boolean }>

export function getZAiModels(apiLine: ZaiApiLine = "international_coding"): Record<string, ModelInfo> {
const isChina = zaiApiLineConfigs[apiLine].isChina
const regionalModels = isChina ? mainlandZAiModels : internationalZAiModels
const codingPlanOnlyModels = isChina ? mainlandZAiCodingPlanOnlyModels : internationalZAiCodingPlanOnlyModels
return apiLine.endsWith("_coding") ? { ...regionalModels, ...codingPlanOnlyModels } : regionalModels
}
2 changes: 1 addition & 1 deletion src/api/providers/__tests__/opencode-go.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -802,7 +802,7 @@ describe("OpencodeGoHandler", () => {
})

it("classifies OpenAI-compatible Go models as non-Anthropic-format", () => {
expect(isOpencodeGoAnthropicFormatModel("glm-5.2")).toBe(false)
expect(isOpencodeGoAnthropicFormatModel("glm-5.3")).toBe(false)
expect(isOpencodeGoAnthropicFormatModel("kimi-k2.6")).toBe(false)
expect(isOpencodeGoAnthropicFormatModel("deepseek-v4-pro")).toBe(false)
expect(isOpencodeGoAnthropicFormatModel("mimo-v2.5")).toBe(false)
Expand Down
99 changes: 99 additions & 0 deletions src/api/providers/__tests__/zai.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
internationalZAiModels,
mainlandZAiModels,
ZAI_DEFAULT_TEMPERATURE,
getZAiModels,
} from "@roo-code/types"

import { ZAiHandler } from "../zai"
Expand Down Expand Up @@ -141,6 +142,30 @@ describe("ZAiHandler", () => {
expect(model.info.cacheReadsPrice).toBe(0.26)
})

it("should expose GLM-5.3 for the international Coding Plan with provisional GLM-5.2 pricing", () => {
const handlerWithModel = new ZAiHandler({
apiModelId: "glm-5.3",
zaiApiKey: "test-zai-api-key",
zaiApiLine: "international_coding",
})
const model = handlerWithModel.getModel()
expect(model.id).toBe("glm-5.3")
expect(model.info).toMatchObject({
contextWindow: 1_000_000,
maxTokens: 131_072,
supportsImages: false,
supportsPromptCache: true,
supportsMaxTokens: true,
supportsReasoningEffort: ["low", "high", "max"],
requiredReasoningEffort: true,
reasoningEffort: "max",
preserveReasoning: 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 @@ -277,6 +302,22 @@ describe("ZAiHandler", () => {
expect(model.info.cacheReadsPrice).toBe(0.13)
})

it("should expose GLM-5.3 for the China Coding Plan", () => {
const handlerWithModel = new ZAiHandler({
apiModelId: "glm-5.3",
zaiApiKey: "test-zai-api-key",
zaiApiLine: "china_coding",
})
const model = handlerWithModel.getModel()
expect(model.id).toBe("glm-5.3")
expect(model.info.supportsReasoningEffort).toEqual(["low", "high", "max"])
expect(model.info.requiredReasoningEffort).toBe(true)
expect(model.info.reasoningEffort).toBe("max")
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 @@ -348,6 +389,16 @@ describe("ZAiHandler", () => {
expect(model.id).toBe(testModelId)
expect(model.info).toEqual(internationalZAiModels[testModelId])
})

it("should not expose Coding Plan-only models", () => {
expect(getZAiModels("international_api")).not.toHaveProperty("glm-5.3")
const handlerWithModel = new ZAiHandler({
apiModelId: "glm-5.3",
zaiApiKey: "test-zai-api-key",
zaiApiLine: "international_api",
})
expect(handlerWithModel.getModel().id).toBe(internationalZAiDefaultModelId)
})
})

describe("China API", () => {
Expand Down Expand Up @@ -387,6 +438,10 @@ describe("ZAiHandler", () => {
expect(model.id).toBe(testModelId)
expect(model.info).toEqual(mainlandZAiModels[testModelId])
})

it("should not expose Coding Plan-only models", () => {
expect(getZAiModels("china_api")).not.toHaveProperty("glm-5.3")
})
})

describe("Default behavior", () => {
Expand Down Expand Up @@ -613,6 +668,50 @@ describe("ZAiHandler", () => {
)
})

it("should keep GLM-5.3 reasoning enabled when a persisted setting requests disable", async () => {
const handlerWithModel = new ZAiHandler({
apiModelId: "glm-5.3",
zaiApiKey: "test-zai-api-key",
zaiApiLine: "international_coding",
reasoningEffort: "disable",
})

mockCreate.mockImplementationOnce(() => asyncStreamFrom([]))

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

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

it("should keep GLM-5.3 reasoning enabled when the master reasoning setting is disabled", async () => {
const handlerWithModel = new ZAiHandler({
apiModelId: "glm-5.3",
zaiApiKey: "test-zai-api-key",
zaiApiLine: "international_coding",
enableReasoningEffort: false,
})

mockCreate.mockImplementationOnce(() => asyncStreamFrom([]))

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

expect(mockCreate).toHaveBeenCalledWith(
expect.objectContaining({
model: "glm-5.3",
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",
Expand Down
4 changes: 2 additions & 2 deletions src/api/providers/fetchers/__tests__/litellm.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -765,7 +765,7 @@ describe("getLiteLLMModels", () => {
data: {
data: [
{
model_name: "glm-5.2",
model_name: "glm-5.3",
model_info: {
max_tokens: 8192,
max_input_tokens: 128000,
Expand All @@ -782,7 +782,7 @@ describe("getLiteLLMModels", () => {

const result = await getLiteLLMModels("test-api-key", "http://localhost:4000")

expect(result["glm-5.2"]).toMatchObject({ preserveReasoning: true })
expect(result["glm-5.3"]).toMatchObject({ preserveReasoning: true })
})

it("does not match a model id that merely contains a known family as a substring", async () => {
Expand Down
14 changes: 14 additions & 0 deletions src/api/providers/fetchers/__tests__/opencode-go.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,20 @@ describe("Opencode Go Fetchers", () => {
expect(info.outputPrice).toBe(4.4)
})

it("resolves GLM-5.3 with always-on Low/High/Max reasoning effort", () => {
const info = parseOpencodeGoModel({ id: "glm-5.3" })
expect(info.contextWindow).toBe(1_000_000)
expect(info.maxTokens).toBe(131_072)
expect(info.supportsPromptCache).toBe(true)
expect(info.supportsMaxTokens).toBe(true)
expect(info.supportsReasoningEffort).toEqual(["low", "high", "max"])
expect(info.reasoningEffort).toBe("max")
expect(info.preserveReasoning).toBe(true)
expect(info.inputPrice).toBe(1.4)
expect(info.outputPrice).toBe(4.4)
expect(info.cacheReadsPrice).toBe(0.26)
})

it("falls back to defaults for an unknown model with no cache pricing", () => {
const info = parseOpencodeGoModel({ id: "x", context_window: 100000, max_tokens: 8000 })
expect(info.supportsPromptCache).toBe(false)
Expand Down
21 changes: 12 additions & 9 deletions src/api/providers/zai.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,12 @@ import { Anthropic } from "@anthropic-ai/sdk"
import OpenAI from "openai"

import {
internationalZAiModels,
mainlandZAiModels,
internationalZAiDefaultModelId,
mainlandZAiDefaultModelId,
type ModelInfo,
ZAI_DEFAULT_TEMPERATURE,
zaiApiLineConfigs,
getZAiModels,
} from "@roo-code/types"

import { type ApiHandlerOptions, getModelMaxOutputTokens } from "../../shared/api"
Expand All @@ -29,14 +28,15 @@ type ZAiChatCompletionParams = Omit<OpenAI.Chat.ChatCompletionCreateParamsStream

export class ZAiHandler extends BaseOpenAiCompatibleProvider<string> {
constructor(options: ApiHandlerOptions) {
const isChina = zaiApiLineConfigs[options.zaiApiLine ?? "international_coding"].isChina
const models = (isChina ? mainlandZAiModels : internationalZAiModels) as unknown as Record<string, ModelInfo>
const apiLine = options.zaiApiLine ?? "international_coding"
const isChina = zaiApiLineConfigs[apiLine].isChina
const models = getZAiModels(apiLine)
const defaultModelId = (isChina ? mainlandZAiDefaultModelId : internationalZAiDefaultModelId) as string

super({
...options,
providerName: "Z.ai",
baseURL: zaiApiLineConfigs[options.zaiApiLine ?? "international_coding"].baseUrl,
baseURL: zaiApiLineConfigs[apiLine].baseUrl,
apiKey: options.zaiApiKey ?? "not-provided",
defaultProviderModelId: defaultModelId,
providerModels: models,
Expand Down Expand Up @@ -85,12 +85,15 @@ export class ZAiHandler extends BaseOpenAiCompatibleProvider<string> {
this.options.enableReasoningEffort === false
? undefined
: (this.options.reasoningEffort ?? info.reasoningEffort)
const requiresReasoning = info.requiredReasoningEffort === true
const effort =
raw && raw !== "disable" && Array.isArray(supported) && !supported.includes(raw)
requiresReasoning && (!raw || raw === "disable")
? info.reasoningEffort
: raw
: raw && Array.isArray(supported) && !supported.includes(raw)
? info.reasoningEffort
: raw
const reasoningEffort = effort && effort !== "disable" ? effort : undefined
const useReasoning = reasoningEffort !== undefined
const useReasoning = requiresReasoning || reasoningEffort !== undefined

const max_tokens =
this.options.modelMaxTokens ||
Expand All @@ -114,7 +117,7 @@ export class ZAiHandler extends BaseOpenAiCompatibleProvider<string> {
messages: [{ role: "system", content: systemPrompt }, ...convertedMessages],
stream: true,
stream_options: { include_usage: true },
// Thinking is ON by default for these models, so explicitly disable it when needed.
// Models with required reasoning stay enabled even when an old setting requests disable.
thinking: useReasoning ? { type: "enabled" } : { type: "disabled" },
reasoning_effort: reasoningEffort,
tools: this.convertToolsForOpenAI(metadata?.tools),
Expand Down
1 change: 1 addition & 0 deletions webview-ui/src/components/settings/ApiOptions.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -722,6 +722,7 @@ const ApiOptions = ({
models={getStaticModelsForProvider(
activeSelectedProvider,
t("settings:labels.useCustomArn"),
apiConfiguration,
)}
modelIdKey="apiModelId"
serviceName={getProviderServiceConfig(activeSelectedProvider).serviceName}
Expand Down
Loading
Loading