Roo-Code/src/api/providers/utils/glm-model-detection.ts
Roo Code d010015905 feat: add strategic GLM model family detection for LM Studio and OpenAI-compatible providers
This PR addresses Issue #11071 by implementing a comprehensive GLM model detection system:

1. Created glm-model-detection.ts utility that:
   - Detects GLM family models (GLM-4.5, 4.6, 4.7 and variants)
   - Supports various model ID formats (standard, MLX, GGUF, ChatGLM)
   - Identifies version (4.5, 4.6, 4.7) and variant (base, air, flash, v, etc.)
   - Returns appropriate configuration for each model

2. Updated LmStudioHandler to:
   - Detect GLM models and log detection results to console
   - Use convertToZAiFormat with mergeToolResultText for GLM models
   - Disable parallel_tool_calls for GLM models
   - Handle reasoning_content for GLM-4.7 models

3. Updated BaseOpenAiCompatibleProvider similarly

4. Added 33 comprehensive tests for the detection utility

The detection uses flexible regex patterns to match model IDs like:
- mlx-community/GLM-4.5-4bit
- GLM-4.5-UD-Q8_K_XL-00001-of-00008.gguf
- glm-4.5, glm-4.7-flash, etc.
2026-01-30 00:41:05 +00:00

199 lines
6.3 KiB
TypeScript

/**
* GLM Model Detection Utility
*
* Detects GLM models from Z.ai (Zhipu AI) and returns appropriate configuration
* for optimal interaction. This utility supports various model ID formats from
* different providers like LM Studio and OpenAI-compatible endpoints.
*
* GLM Model Family:
* - GLM-4.5: Base model with 355B parameters
* - GLM-4.5-Air: Lightweight version balancing performance and cost
* - GLM-4.5-X: High-performance variant with ultra-fast responses
* - GLM-4.5-AirX: Lightweight ultra-fast variant
* - GLM-4.5-Flash: Free high-speed model
* - GLM-4.5V: Multimodal visual model
* - GLM-4.6: Extended 200k context window
* - GLM-4.6V: Multimodal vision model
* - GLM-4.6V-Flash: Free high-speed vision model
* - GLM-4.7: Built-in thinking capabilities
* - GLM-4.7-Flash: Free high-speed variant of GLM-4.7
* - GLM-4.7-FlashX: Ultra-fast variant
*/
/**
* GLM model version enumeration
*/
export type GlmVersion = "4.5" | "4.6" | "4.7" | "unknown"
/**
* GLM model variant - specific model within a version
*/
export type GlmVariant =
| "base"
| "air"
| "x"
| "airx"
| "flash"
| "flashx"
| "v" // vision
| "v-flash"
| "v-flashx"
/**
* Configuration options for GLM models
*/
export interface GlmModelConfig {
/** Whether this is a GLM model */
isGlmModel: boolean
/** The detected GLM version (4.5, 4.6, 4.7) */
version: GlmVersion
/** The detected variant (base, air, flash, v, etc.) */
variant: GlmVariant | "unknown"
/** Whether this model supports vision/images */
supportsVision: boolean
/** Whether this model has built-in thinking/reasoning support */
supportsThinking: boolean
/** Whether to merge tool result text into tool messages */
mergeToolResultText: boolean
/** Whether to disable parallel tool calls */
disableParallelToolCalls: boolean
/** The original model ID */
originalModelId: string
/** A normalized/canonical model name for display */
displayName: string
}
/**
* Detects if a model ID represents a GLM model and returns its configuration.
*
* Supports various model ID formats:
* - Standard: "glm-4.5", "glm-4.7-flash"
* - With prefix: "mlx-community/GLM-4.5-4bit"
* - GGUF files: "GLM-4.5-UD-Q8_K_XL-00001-of-00008.gguf"
* - ChatGLM: "chatglm-6b", "chatglm3-6b"
*
* @param modelId The model identifier string
* @returns GLM model configuration
*/
export function detectGlmModel(modelId: string): GlmModelConfig {
const lowerModelId = modelId.toLowerCase()
// Check if this is a GLM model using case-insensitive matching
// Match patterns: "glm-", "glm4", "chatglm", or "glm" followed by a version number
const isGlm = /glm[-_]?4|chatglm|\/glm[-_]|^glm[-_]/i.test(modelId)
if (!isGlm) {
return {
isGlmModel: false,
version: "unknown",
variant: "unknown",
supportsVision: false,
supportsThinking: false,
mergeToolResultText: false,
disableParallelToolCalls: false,
originalModelId: modelId,
displayName: modelId,
}
}
// Detect version (4.5, 4.6, 4.7)
let version: GlmVersion = "unknown"
if (/4\.7|4-7|47/i.test(lowerModelId)) {
version = "4.7"
} else if (/4\.6|4-6|46/i.test(lowerModelId)) {
version = "4.6"
} else if (/4\.5|4-5|45|4p5/i.test(lowerModelId)) {
version = "4.5"
}
// Detect variant
let variant: GlmVariant = "base"
let supportsVision = false
// Check for vision variants first (they may also have flash/etc.)
if (/4\.5v|4-5v|45v|4p5v|glm-4\.5v/i.test(lowerModelId)) {
variant = "v"
supportsVision = true
} else if (/4\.6v[-_]?flashx|4-6v[-_]?flashx/i.test(lowerModelId)) {
variant = "v-flashx"
supportsVision = true
} else if (/4\.6v[-_]?flash|4-6v[-_]?flash/i.test(lowerModelId)) {
variant = "v-flash"
supportsVision = true
} else if (/4\.6v|4-6v|46v/i.test(lowerModelId)) {
variant = "v"
supportsVision = true
}
// Non-vision variants
else if (/flashx/i.test(lowerModelId)) {
variant = "flashx"
} else if (/flash/i.test(lowerModelId)) {
variant = "flash"
} else if (/airx/i.test(lowerModelId)) {
variant = "airx"
} else if (/air/i.test(lowerModelId)) {
variant = "air"
} else if (/[-_]x\b/i.test(lowerModelId)) {
// Match "-x" or "_x" at word boundary (to avoid matching "flashx", "airx")
variant = "x"
}
// GLM-4.7 has built-in thinking support
const supportsThinking = version === "4.7"
// Generate display name
let displayName = `GLM-${version !== "unknown" ? version : "4.x"}`
if (variant !== "base") {
const variantName = variant.toUpperCase().replace("-", " ")
displayName += ` ${variantName}`
}
return {
isGlmModel: true,
version,
variant,
supportsVision,
supportsThinking,
// All GLM models benefit from mergeToolResultText to prevent reasoning_content loss
mergeToolResultText: true,
// Disable parallel tool calls for GLM models as they may not support it properly
disableParallelToolCalls: true,
originalModelId: modelId,
displayName,
}
}
/**
* Logs GLM model detection results to the console for debugging.
*
* @param providerName The name of the provider (e.g., "LM Studio", "OpenAI-compatible")
* @param modelId The model ID being used
* @param config The detected GLM configuration
*/
export function logGlmDetection(providerName: string, modelId: string, config: GlmModelConfig): void {
console.log(`[${providerName}] Using model ID: "${modelId}"`)
if (config.isGlmModel) {
console.log(`[GLM Detection] ✓ GLM model detected: "${modelId}"`)
console.log(`[GLM Detection] - Version: ${config.version}`)
console.log(`[GLM Detection] - Variant: ${config.variant}`)
console.log(`[GLM Detection] - Display name: ${config.displayName}`)
console.log(`[GLM Detection] - Supports vision: ${config.supportsVision}`)
console.log(`[GLM Detection] - Supports thinking: ${config.supportsThinking}`)
console.log(`[GLM Detection] - mergeToolResultText: ${config.mergeToolResultText}`)
console.log(`[GLM Detection] - disableParallelToolCalls: ${config.disableParallelToolCalls}`)
} else {
console.log(`[GLM Detection] ✗ Not a GLM model: "${modelId}"`)
}
}
/**
* Simple check if a model ID is a GLM model without full configuration.
* Use this for quick checks where you only need a boolean.
*
* @param modelId The model identifier string
* @returns true if the model is a GLM model
*/
export function isGlmModel(modelId: string): boolean {
return /glm[-_]?4|chatglm|\/glm[-_]|^glm[-_]/i.test(modelId)
}