* feat(zai): add Z.AI GLM Coding Plan provider preset Add dedicated Z.AI provider support for the GLM Coding Plan, enabling use of GLM-5.1, GLM-5-Turbo, GLM-4.7, and GLM-4.5-Air models through the OpenAI-compatible shim with proper thinking mode (reasoning_content), max_tokens handling, and context window sizing. * fix(zai): unify GLM max output token limits across casing variants glm-5/glm-4.7 had conservative 16K max output while GLM-5/GLM-4.7 had 131K. Use consistent Z.AI coding plan limits for all GLM variants. * fix(zai): restore DashScope GLM limits, enable GLM thinking support - Restore lowercase glm-5/glm-4.7 to 16_384 max output (DashScope limits) while keeping Z.AI coding plan high limits on uppercase GLM-* keys only - Add GLM model support to modelSupportsThinking() so reasoning_content is enabled when using GLM-5.x/GLM-4.7 models on Z.AI * fix(zai): tighten GLM regexes, fix misleading context window comment - Use precise regex in thinking.ts: exact GLM model matches only, no false positives on glm-50/glm-4, includes glm-4.5-air - Use uppercase-only match in StartupScreen rawModel fallback so DashScope lowercase glm-* models aren't mislabeled as Z.AI - Clarify context window comment: lowercase glm-5.1/glm-5-turbo/ glm-4.5-air are Z.AI-specific aliases, not DashScope * fix(zai): scope GLM detection to Z.AI * improve readability of max_completion_tokens check Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
177 lines
5.7 KiB
TypeScript
177 lines
5.7 KiB
TypeScript
// biome-ignore-all assist/source/organizeImports: internal-only import markers must not be reordered
|
|
import type { Theme } from './theme.js'
|
|
import { feature } from 'bun:bundle'
|
|
import { getFeatureValue_CACHED_MAY_BE_STALE } from '../services/analytics/growthbook.js'
|
|
import { getCanonicalName } from './model/model.js'
|
|
import { get3PModelCapabilityOverride } from './model/modelSupportOverrides.js'
|
|
import { getAPIProvider } from './model/providers.js'
|
|
import { getSettingsWithErrors } from './settings/settings.js'
|
|
import { isZaiBaseUrl, isZaiGlmModel } from './zaiProvider.js'
|
|
|
|
export type ThinkingConfig =
|
|
| { type: 'adaptive' }
|
|
| { type: 'enabled'; budgetTokens: number }
|
|
| { type: 'disabled' }
|
|
|
|
/**
|
|
* Build-time gate (feature) + runtime gate (GrowthBook). The build flag
|
|
* controls code inclusion in external builds; the GB flag controls rollout.
|
|
*/
|
|
export function isUltrathinkEnabled(): boolean {
|
|
if (!feature('ULTRATHINK')) {
|
|
return false
|
|
}
|
|
return getFeatureValue_CACHED_MAY_BE_STALE('tengu_turtle_carbon', true)
|
|
}
|
|
|
|
/**
|
|
* Check if text contains the "ultrathink" keyword.
|
|
*/
|
|
export function hasUltrathinkKeyword(text: string): boolean {
|
|
return /\bultrathink\b/i.test(text)
|
|
}
|
|
|
|
/**
|
|
* Find positions of "ultrathink" keyword in text (for UI highlighting/notification)
|
|
*/
|
|
export function findThinkingTriggerPositions(text: string): Array<{
|
|
word: string
|
|
start: number
|
|
end: number
|
|
}> {
|
|
const positions: Array<{ word: string; start: number; end: number }> = []
|
|
// Fresh /g literal each call — String.prototype.matchAll copies lastIndex
|
|
// from the source regex, so a shared instance would leak state from
|
|
// hasUltrathinkKeyword's .test() into this call on the next render.
|
|
const matches = text.matchAll(/\bultrathink\b/gi)
|
|
|
|
for (const match of matches) {
|
|
if (match.index !== undefined) {
|
|
positions.push({
|
|
word: match[0],
|
|
start: match.index,
|
|
end: match.index + match[0].length,
|
|
})
|
|
}
|
|
}
|
|
|
|
return positions
|
|
}
|
|
|
|
const RAINBOW_COLORS: Array<keyof Theme> = [
|
|
'rainbow_red',
|
|
'rainbow_orange',
|
|
'rainbow_yellow',
|
|
'rainbow_green',
|
|
'rainbow_blue',
|
|
'rainbow_indigo',
|
|
'rainbow_violet',
|
|
]
|
|
|
|
const RAINBOW_SHIMMER_COLORS: Array<keyof Theme> = [
|
|
'rainbow_red_shimmer',
|
|
'rainbow_orange_shimmer',
|
|
'rainbow_yellow_shimmer',
|
|
'rainbow_green_shimmer',
|
|
'rainbow_blue_shimmer',
|
|
'rainbow_indigo_shimmer',
|
|
'rainbow_violet_shimmer',
|
|
]
|
|
|
|
export function getRainbowColor(
|
|
charIndex: number,
|
|
shimmer: boolean = false,
|
|
): keyof Theme {
|
|
const colors = shimmer ? RAINBOW_SHIMMER_COLORS : RAINBOW_COLORS
|
|
return colors[charIndex % colors.length]!
|
|
}
|
|
|
|
// TODO(inigo): add support for probing unknown models via API error detection
|
|
// Provider-aware thinking support detection (aligns with modelSupportsISP in betas.ts)
|
|
export function modelSupportsThinking(model: string): boolean {
|
|
const supported3P = get3PModelCapabilityOverride(model, 'thinking')
|
|
if (supported3P !== undefined) {
|
|
return supported3P
|
|
}
|
|
if (process.env.USER_TYPE === 'ant') {
|
|
if (resolveAntModel(model.toLowerCase())) {
|
|
return true
|
|
}
|
|
}
|
|
// IMPORTANT: Do not change thinking support without notifying the model
|
|
// launch DRI and research. This can greatly affect model quality and bashing.
|
|
const canonical = getCanonicalName(model)
|
|
const provider = getAPIProvider()
|
|
// 1P and Foundry: all Claude 4+ models (including Haiku 4.5)
|
|
if (provider === 'foundry' || provider === 'firstParty') {
|
|
return !canonical.includes('claude-3-')
|
|
}
|
|
if (
|
|
canonical.startsWith('deepseek-v4-') ||
|
|
canonical === 'deepseek-reasoner'
|
|
) {
|
|
return true
|
|
}
|
|
if (
|
|
provider === 'openai' &&
|
|
isZaiBaseUrl(process.env.OPENAI_BASE_URL ?? process.env.OPENAI_API_BASE) &&
|
|
isZaiGlmModel(canonical)
|
|
) {
|
|
return true
|
|
}
|
|
// 3P (Bedrock/Vertex): only Opus 4+ and Sonnet 4+
|
|
return canonical.includes('sonnet-4') || canonical.includes('opus-4')
|
|
}
|
|
|
|
// @[MODEL LAUNCH]: Add the new model to the allowlist if it supports adaptive thinking.
|
|
export function modelSupportsAdaptiveThinking(model: string): boolean {
|
|
const supported3P = get3PModelCapabilityOverride(model, 'adaptive_thinking')
|
|
if (supported3P !== undefined) {
|
|
return supported3P
|
|
}
|
|
const canonical = getCanonicalName(model)
|
|
// Supported by a subset of Claude 4 models
|
|
if (canonical.includes('opus-4-6') || canonical.includes('sonnet-4-6')) {
|
|
return true
|
|
}
|
|
// Exclude any other known legacy models (allowlist above catches 4-6 variants first)
|
|
if (
|
|
canonical.includes('opus') ||
|
|
canonical.includes('sonnet') ||
|
|
canonical.includes('haiku')
|
|
) {
|
|
return false
|
|
}
|
|
// IMPORTANT: Do not change adaptive thinking support without notifying the
|
|
// model launch DRI and research. This can greatly affect model quality and
|
|
// bashing.
|
|
|
|
// Newer models (4.6+) are all trained on adaptive thinking and MUST have it
|
|
// enabled for model testing. DO NOT default to false for first party, otherwise
|
|
// we may silently degrade model quality.
|
|
|
|
// Default to true for unknown model strings on 1P and Foundry (because Foundry
|
|
// is a proxy). Do not default to true for other 3P as they have different formats
|
|
// for their model strings.
|
|
const provider = getAPIProvider()
|
|
return provider === 'firstParty' || provider === 'foundry'
|
|
}
|
|
|
|
export function shouldEnableThinkingByDefault(): boolean {
|
|
if (process.env.MAX_THINKING_TOKENS) {
|
|
return parseInt(process.env.MAX_THINKING_TOKENS, 10) > 0
|
|
}
|
|
|
|
const { settings } = getSettingsWithErrors()
|
|
if (settings.alwaysThinkingEnabled === false) {
|
|
return false
|
|
}
|
|
|
|
// IMPORTANT: Do not change default thinking enabled value without notifying
|
|
// the model launch DRI and research. This can greatly affect model quality and
|
|
// bashing.
|
|
|
|
// Enable thinking by default unless explicitly disabled.
|
|
return true
|
|
}
|