mirror of
https://github.com/CherryHQ/cherry-studio.git
synced 2026-01-02 18:39:06 +08:00
feat(gemini): update model types and add support for gemini3 variants
add new model type identifiers for gemini3 flash and pro variants implement utility functions to detect gemini3 flash and pro models update reasoning configuration and tests for new gemini variants
This commit is contained in:
parent
0cf0072b51
commit
b001a7aa7a
@ -949,6 +949,14 @@ describe('Gemini Models', () => {
|
||||
group: ''
|
||||
})
|
||||
).toBe(true)
|
||||
expect(
|
||||
isSupportedThinkingTokenGeminiModel({
|
||||
id: 'gemini-3-flash-preview',
|
||||
name: '',
|
||||
provider: '',
|
||||
group: ''
|
||||
})
|
||||
).toBe(true)
|
||||
expect(
|
||||
isSupportedThinkingTokenGeminiModel({
|
||||
id: 'google/gemini-3-pro-preview',
|
||||
@ -990,6 +998,31 @@ describe('Gemini Models', () => {
|
||||
group: ''
|
||||
})
|
||||
).toBe(true)
|
||||
// Version with date suffixes
|
||||
expect(
|
||||
isSupportedThinkingTokenGeminiModel({
|
||||
id: 'gemini-3-flash-preview-09-2025',
|
||||
name: '',
|
||||
provider: '',
|
||||
group: ''
|
||||
})
|
||||
).toBe(true)
|
||||
expect(
|
||||
isSupportedThinkingTokenGeminiModel({
|
||||
id: 'gemini-3-pro-preview-09-2025',
|
||||
name: '',
|
||||
provider: '',
|
||||
group: ''
|
||||
})
|
||||
).toBe(true)
|
||||
expect(
|
||||
isSupportedThinkingTokenGeminiModel({
|
||||
id: 'gemini-3-flash-exp-1234',
|
||||
name: '',
|
||||
provider: '',
|
||||
group: ''
|
||||
})
|
||||
).toBe(true)
|
||||
// Version with decimals
|
||||
expect(
|
||||
isSupportedThinkingTokenGeminiModel({
|
||||
@ -1009,7 +1042,8 @@ describe('Gemini Models', () => {
|
||||
).toBe(true)
|
||||
})
|
||||
|
||||
it('should return true for gemini-3 image models', () => {
|
||||
it('should return true for gemini-3-pro-image models only', () => {
|
||||
// Only gemini-3-pro-image models should return true
|
||||
expect(
|
||||
isSupportedThinkingTokenGeminiModel({
|
||||
id: 'gemini-3-pro-image-preview',
|
||||
@ -1018,6 +1052,17 @@ describe('Gemini Models', () => {
|
||||
group: ''
|
||||
})
|
||||
).toBe(true)
|
||||
expect(
|
||||
isSupportedThinkingTokenGeminiModel({
|
||||
id: 'gemini-3-pro-image',
|
||||
name: '',
|
||||
provider: '',
|
||||
group: ''
|
||||
})
|
||||
).toBe(true)
|
||||
})
|
||||
|
||||
it('should return false for other gemini-3 image models', () => {
|
||||
expect(
|
||||
isSupportedThinkingTokenGeminiModel({
|
||||
id: 'gemini-3.0-flash-image-preview',
|
||||
@ -1080,6 +1125,22 @@ describe('Gemini Models', () => {
|
||||
group: ''
|
||||
})
|
||||
).toBe(false)
|
||||
expect(
|
||||
isSupportedThinkingTokenGeminiModel({
|
||||
id: 'gemini-3-flash-preview-tts',
|
||||
name: '',
|
||||
provider: '',
|
||||
group: ''
|
||||
})
|
||||
).toBe(false)
|
||||
expect(
|
||||
isSupportedThinkingTokenGeminiModel({
|
||||
id: 'gemini-3-pro-tts',
|
||||
name: '',
|
||||
provider: '',
|
||||
group: ''
|
||||
})
|
||||
).toBe(false)
|
||||
})
|
||||
|
||||
it('should return false for older gemini models', () => {
|
||||
@ -2072,7 +2133,7 @@ describe('getModelSupportedReasoningEffortOptions', () => {
|
||||
|
||||
const geminiModel = createModel({ id: 'gemini-2.5-flash-latest' })
|
||||
const geminiResult = getModelSupportedReasoningEffortOptions(geminiModel)
|
||||
expect(geminiResult).toEqual(MODEL_SUPPORTED_OPTIONS.gemini)
|
||||
expect(geminiResult).toEqual(MODEL_SUPPORTED_OPTIONS.gemini2_flash)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@ -20,6 +20,8 @@ import {
|
||||
getModelSupportedVerbosity,
|
||||
groupQwenModels,
|
||||
isAnthropicModel,
|
||||
isGemini3FlashModel,
|
||||
isGemini3ProModel,
|
||||
isGeminiModel,
|
||||
isGemmaModel,
|
||||
isGenerateImageModels,
|
||||
@ -432,6 +434,101 @@ describe('model utils', () => {
|
||||
})
|
||||
})
|
||||
|
||||
describe('isGemini3FlashModel', () => {
|
||||
it('detects gemini-3-flash model', () => {
|
||||
expect(isGemini3FlashModel(createModel({ id: 'gemini-3-flash' }))).toBe(true)
|
||||
})
|
||||
|
||||
it('detects gemini-3-flash-preview model', () => {
|
||||
expect(isGemini3FlashModel(createModel({ id: 'gemini-3-flash-preview' }))).toBe(true)
|
||||
})
|
||||
|
||||
it('detects gemini-3-flash with version suffixes', () => {
|
||||
expect(isGemini3FlashModel(createModel({ id: 'gemini-3-flash-latest' }))).toBe(true)
|
||||
expect(isGemini3FlashModel(createModel({ id: 'gemini-3-flash-preview-09-2025' }))).toBe(true)
|
||||
expect(isGemini3FlashModel(createModel({ id: 'gemini-3-flash-exp-1234' }))).toBe(true)
|
||||
})
|
||||
|
||||
it('detects gemini-flash-latest alias', () => {
|
||||
expect(isGemini3FlashModel(createModel({ id: 'gemini-flash-latest' }))).toBe(true)
|
||||
expect(isGemini3FlashModel(createModel({ id: 'Gemini-Flash-Latest' }))).toBe(true)
|
||||
})
|
||||
|
||||
it('detects gemini-3-flash with uppercase', () => {
|
||||
expect(isGemini3FlashModel(createModel({ id: 'Gemini-3-Flash' }))).toBe(true)
|
||||
expect(isGemini3FlashModel(createModel({ id: 'GEMINI-3-FLASH-PREVIEW' }))).toBe(true)
|
||||
})
|
||||
|
||||
it('excludes gemini-3-flash-image models', () => {
|
||||
expect(isGemini3FlashModel(createModel({ id: 'gemini-3-flash-image-preview' }))).toBe(false)
|
||||
expect(isGemini3FlashModel(createModel({ id: 'gemini-3-flash-image' }))).toBe(false)
|
||||
})
|
||||
|
||||
it('returns false for non-flash gemini-3 models', () => {
|
||||
expect(isGemini3FlashModel(createModel({ id: 'gemini-3-pro' }))).toBe(false)
|
||||
expect(isGemini3FlashModel(createModel({ id: 'gemini-3-pro-preview' }))).toBe(false)
|
||||
expect(isGemini3FlashModel(createModel({ id: 'gemini-3-pro-image-preview' }))).toBe(false)
|
||||
})
|
||||
|
||||
it('returns false for other gemini models', () => {
|
||||
expect(isGemini3FlashModel(createModel({ id: 'gemini-2-flash' }))).toBe(false)
|
||||
expect(isGemini3FlashModel(createModel({ id: 'gemini-2-flash-preview' }))).toBe(false)
|
||||
expect(isGemini3FlashModel(createModel({ id: 'gemini-2.5-flash-preview-09-2025' }))).toBe(false)
|
||||
})
|
||||
|
||||
it('returns false for null/undefined models', () => {
|
||||
expect(isGemini3FlashModel(null)).toBe(false)
|
||||
expect(isGemini3FlashModel(undefined)).toBe(false)
|
||||
})
|
||||
})
|
||||
|
||||
describe('isGemini3ProModel', () => {
|
||||
it('detects gemini-3-pro model', () => {
|
||||
expect(isGemini3ProModel(createModel({ id: 'gemini-3-pro' }))).toBe(true)
|
||||
})
|
||||
|
||||
it('detects gemini-3-pro-preview model', () => {
|
||||
expect(isGemini3ProModel(createModel({ id: 'gemini-3-pro-preview' }))).toBe(true)
|
||||
})
|
||||
|
||||
it('detects gemini-3-pro with version suffixes', () => {
|
||||
expect(isGemini3ProModel(createModel({ id: 'gemini-3-pro-latest' }))).toBe(true)
|
||||
expect(isGemini3ProModel(createModel({ id: 'gemini-3-pro-preview-09-2025' }))).toBe(true)
|
||||
expect(isGemini3ProModel(createModel({ id: 'gemini-3-pro-exp-1234' }))).toBe(true)
|
||||
})
|
||||
|
||||
it('detects gemini-pro-latest alias', () => {
|
||||
expect(isGemini3ProModel(createModel({ id: 'gemini-pro-latest' }))).toBe(true)
|
||||
expect(isGemini3ProModel(createModel({ id: 'Gemini-Pro-Latest' }))).toBe(true)
|
||||
})
|
||||
|
||||
it('detects gemini-3-pro with uppercase', () => {
|
||||
expect(isGemini3ProModel(createModel({ id: 'Gemini-3-Pro' }))).toBe(true)
|
||||
expect(isGemini3ProModel(createModel({ id: 'GEMINI-3-PRO-PREVIEW' }))).toBe(true)
|
||||
})
|
||||
|
||||
it('excludes gemini-3-pro-image models', () => {
|
||||
expect(isGemini3ProModel(createModel({ id: 'gemini-3-pro-image-preview' }))).toBe(false)
|
||||
expect(isGemini3ProModel(createModel({ id: 'gemini-3-pro-image' }))).toBe(false)
|
||||
expect(isGemini3ProModel(createModel({ id: 'gemini-3-pro-image-latest' }))).toBe(false)
|
||||
})
|
||||
|
||||
it('returns false for non-pro gemini-3 models', () => {
|
||||
expect(isGemini3ProModel(createModel({ id: 'gemini-3-flash' }))).toBe(false)
|
||||
expect(isGemini3ProModel(createModel({ id: 'gemini-3-flash-preview' }))).toBe(false)
|
||||
})
|
||||
|
||||
it('returns false for other gemini models', () => {
|
||||
expect(isGemini3ProModel(createModel({ id: 'gemini-2-pro' }))).toBe(false)
|
||||
expect(isGemini3ProModel(createModel({ id: 'gemini-2.5-pro-preview-09-2025' }))).toBe(false)
|
||||
})
|
||||
|
||||
it('returns false for null/undefined models', () => {
|
||||
expect(isGemini3ProModel(null)).toBe(false)
|
||||
expect(isGemini3ProModel(undefined)).toBe(false)
|
||||
})
|
||||
})
|
||||
|
||||
describe('isZhipuModel', () => {
|
||||
it('detects Zhipu models by provider', () => {
|
||||
expect(isZhipuModel(createModel({ provider: 'zhipu' }))).toBe(true)
|
||||
|
||||
@ -20,7 +20,7 @@ import {
|
||||
isOpenAIReasoningModel,
|
||||
isSupportedReasoningEffortOpenAIModel
|
||||
} from './openai'
|
||||
import { GEMINI_FLASH_MODEL_REGEX, isGemini3ThinkingTokenModel } from './utils'
|
||||
import { GEMINI_FLASH_MODEL_REGEX, isGemini3FlashModel, isGemini3ProModel } from './utils'
|
||||
import { isTextToImageModel } from './vision'
|
||||
|
||||
// Reasoning models
|
||||
@ -43,9 +43,10 @@ export const MODEL_SUPPORTED_REASONING_EFFORT = {
|
||||
gpt52pro: ['medium', 'high', 'xhigh'] as const,
|
||||
grok: ['low', 'high'] as const,
|
||||
grok4_fast: ['auto'] as const,
|
||||
gemini: ['low', 'medium', 'high', 'auto'] as const,
|
||||
gemini3: ['low', 'medium', 'high'] as const,
|
||||
gemini_pro: ['low', 'medium', 'high', 'auto'] as const,
|
||||
gemini2_flash: ['low', 'medium', 'high', 'auto'] as const,
|
||||
gemini2_pro: ['low', 'medium', 'high', 'auto'] as const,
|
||||
gemini3_flash: ['minimal', 'low', 'medium', 'high'] as const,
|
||||
gemini3_pro: ['low', 'high'] as const,
|
||||
qwen: ['low', 'medium', 'high'] as const,
|
||||
qwen_thinking: ['low', 'medium', 'high'] as const,
|
||||
doubao: ['auto', 'high'] as const,
|
||||
@ -72,9 +73,10 @@ export const MODEL_SUPPORTED_OPTIONS: ThinkingOptionConfig = {
|
||||
gpt52pro: ['default', ...MODEL_SUPPORTED_REASONING_EFFORT.gpt52pro] as const,
|
||||
grok: ['default', ...MODEL_SUPPORTED_REASONING_EFFORT.grok] as const,
|
||||
grok4_fast: ['default', 'none', ...MODEL_SUPPORTED_REASONING_EFFORT.grok4_fast] as const,
|
||||
gemini: ['default', 'none', ...MODEL_SUPPORTED_REASONING_EFFORT.gemini] as const,
|
||||
gemini_pro: ['default', ...MODEL_SUPPORTED_REASONING_EFFORT.gemini_pro] as const,
|
||||
gemini3: ['default', ...MODEL_SUPPORTED_REASONING_EFFORT.gemini3] as const,
|
||||
gemini2_flash: ['default', 'none', ...MODEL_SUPPORTED_REASONING_EFFORT.gemini2_flash] as const,
|
||||
gemini2_pro: ['default', ...MODEL_SUPPORTED_REASONING_EFFORT.gemini2_pro] as const,
|
||||
gemini3_flash: ['default', ...MODEL_SUPPORTED_REASONING_EFFORT.gemini3_flash] as const,
|
||||
gemini3_pro: ['default', ...MODEL_SUPPORTED_REASONING_EFFORT.gemini3_pro] as const,
|
||||
qwen: ['default', 'none', ...MODEL_SUPPORTED_REASONING_EFFORT.qwen] as const,
|
||||
qwen_thinking: ['default', ...MODEL_SUPPORTED_REASONING_EFFORT.qwen_thinking] as const,
|
||||
doubao: ['default', 'none', ...MODEL_SUPPORTED_REASONING_EFFORT.doubao] as const,
|
||||
@ -100,8 +102,7 @@ const _getThinkModelType = (model: Model): ThinkingModelType => {
|
||||
const modelId = getLowerBaseModelName(model.id)
|
||||
if (isOpenAIDeepResearchModel(model)) {
|
||||
return 'openai_deep_research'
|
||||
}
|
||||
if (isGPT51SeriesModel(model)) {
|
||||
} else if (isGPT51SeriesModel(model)) {
|
||||
if (modelId.includes('codex')) {
|
||||
thinkingModelType = 'gpt5_1_codex'
|
||||
if (isGPT51CodexMaxModel(model)) {
|
||||
@ -129,16 +130,18 @@ const _getThinkModelType = (model: Model): ThinkingModelType => {
|
||||
} else if (isGrok4FastReasoningModel(model)) {
|
||||
thinkingModelType = 'grok4_fast'
|
||||
} else if (isSupportedThinkingTokenGeminiModel(model)) {
|
||||
if (GEMINI_FLASH_MODEL_REGEX.test(model.id)) {
|
||||
thinkingModelType = 'gemini'
|
||||
if (isGemini3FlashModel(model)) {
|
||||
thinkingModelType = 'gemini3_flash'
|
||||
} else if (isGemini3ProModel(model)) {
|
||||
thinkingModelType = 'gemini3_pro'
|
||||
} else if (GEMINI_FLASH_MODEL_REGEX.test(model.id)) {
|
||||
thinkingModelType = 'gemini2_flash'
|
||||
} else {
|
||||
thinkingModelType = 'gemini_pro'
|
||||
thinkingModelType = 'gemini2_pro'
|
||||
}
|
||||
if (isGemini3ThinkingTokenModel(model)) {
|
||||
thinkingModelType = 'gemini3'
|
||||
}
|
||||
} else if (isSupportedReasoningEffortGrokModel(model)) thinkingModelType = 'grok'
|
||||
else if (isSupportedThinkingTokenQwenModel(model)) {
|
||||
} else if (isSupportedReasoningEffortGrokModel(model)) {
|
||||
thinkingModelType = 'grok'
|
||||
} else if (isSupportedThinkingTokenQwenModel(model)) {
|
||||
if (isQwenAlwaysThinkModel(model)) {
|
||||
thinkingModelType = 'qwen_thinking'
|
||||
}
|
||||
@ -151,10 +154,15 @@ const _getThinkModelType = (model: Model): ThinkingModelType => {
|
||||
} else {
|
||||
thinkingModelType = 'doubao_no_auto'
|
||||
}
|
||||
} else if (isSupportedThinkingTokenHunyuanModel(model)) thinkingModelType = 'hunyuan'
|
||||
else if (isSupportedReasoningEffortPerplexityModel(model)) thinkingModelType = 'perplexity'
|
||||
else if (isSupportedThinkingTokenZhipuModel(model)) thinkingModelType = 'zhipu'
|
||||
else if (isDeepSeekHybridInferenceModel(model)) thinkingModelType = 'deepseek_hybrid'
|
||||
} else if (isSupportedThinkingTokenHunyuanModel(model)) {
|
||||
thinkingModelType = 'hunyuan'
|
||||
} else if (isSupportedReasoningEffortPerplexityModel(model)) {
|
||||
thinkingModelType = 'perplexity'
|
||||
} else if (isSupportedThinkingTokenZhipuModel(model)) {
|
||||
thinkingModelType = 'zhipu'
|
||||
} else if (isDeepSeekHybridInferenceModel(model)) {
|
||||
thinkingModelType = 'deepseek_hybrid'
|
||||
}
|
||||
return thinkingModelType
|
||||
}
|
||||
|
||||
|
||||
@ -267,3 +267,43 @@ export const isGemini3ThinkingTokenModel = (model: Model) => {
|
||||
const modelId = getLowerBaseModelName(model.id)
|
||||
return isGemini3Model(model) && !modelId.includes('image')
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the model is a Gemini 3 Flash model
|
||||
* Matches: gemini-3-flash, gemini-3-flash-preview, gemini-3-flash-preview-09-2025, gemini-flash-latest (alias)
|
||||
* Excludes: gemini-3-flash-image-preview
|
||||
* @param model - The model to check
|
||||
* @returns true if the model is a Gemini 3 Flash model
|
||||
*/
|
||||
export const isGemini3FlashModel = (model: Model | undefined | null): boolean => {
|
||||
if (!model) {
|
||||
return false
|
||||
}
|
||||
const modelId = getLowerBaseModelName(model.id)
|
||||
// Check for gemini-flash-latest alias (currently points to gemini-3-flash, may change in future)
|
||||
if (modelId === 'gemini-flash-latest') {
|
||||
return true
|
||||
}
|
||||
// Check for gemini-3-flash with optional suffixes, excluding image variants
|
||||
return /gemini-3-flash(?!-image)(?:-[\w-]+)*$/i.test(modelId)
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the model is a Gemini 3 Pro model
|
||||
* Matches: gemini-3-pro, gemini-3-pro-preview, gemini-3-pro-preview-09-2025, gemini-pro-latest (alias)
|
||||
* Excludes: gemini-3-pro-image-preview
|
||||
* @param model - The model to check
|
||||
* @returns true if the model is a Gemini 3 Pro model
|
||||
*/
|
||||
export const isGemini3ProModel = (model: Model | undefined | null): boolean => {
|
||||
if (!model) {
|
||||
return false
|
||||
}
|
||||
const modelId = getLowerBaseModelName(model.id)
|
||||
// Check for gemini-pro-latest alias (currently points to gemini-3-pro, may change in future)
|
||||
if (modelId === 'gemini-pro-latest') {
|
||||
return true
|
||||
}
|
||||
// Check for gemini-3-pro with optional suffixes, excluding image variants
|
||||
return /gemini-3-pro(?!-image)(?:-[\w-]+)*$/i.test(modelId)
|
||||
}
|
||||
|
||||
@ -94,9 +94,10 @@ const ThinkModelTypes = [
|
||||
'gpt52pro',
|
||||
'grok',
|
||||
'grok4_fast',
|
||||
'gemini',
|
||||
'gemini_pro',
|
||||
'gemini3',
|
||||
'gemini2_flash',
|
||||
'gemini2_pro',
|
||||
'gemini3_flash',
|
||||
'gemini3_pro',
|
||||
'qwen',
|
||||
'qwen_thinking',
|
||||
'doubao',
|
||||
|
||||
Loading…
Reference in New Issue
Block a user