feat: refresh built-in model catalog
This commit is contained in:
1 parent
e4a281f42d
commit
bfd1543110
23 files changed
+195
-117
No files matched your search
@@ -142,9 +142,12 @@ export class ImageModelManager implements IImageModelManager {
|
||||
defaultConfig: ImageModelConfig
|
||||
): ImageModelConfig {
|
||||
const legacyDefaultIds: Record<string, readonly string[]> = {
|
||||
'image-openrouter-nanobanana': ['google/gemini-2.5-flash-image'],
|
||||
'image-gemini-nanobanana': ['gemini-2.5-flash-image', 'gemini-3.1-flash-image-preview'],
|
||||
'image-dashscope': ['qwen-image'],
|
||||
'image-seedream-50-lite': ['doubao-seedream-5-0-260128']
|
||||
'image-openai-gpt': ['gpt-image-2'],
|
||||
'image-dashscope': ['qwen-image', 'qwen-image-2.0'],
|
||||
'image-seedream-50-lite': ['doubao-seedream-5-0-260128'],
|
||||
'image-grok-imagine': ['grok-imagine-image-quality']
|
||||
}
|
||||
const currentModelId = config.modelId || config.model?.id
|
||||
if (!currentModelId || !legacyDefaultIds[key]?.includes(currentModelId)) {
|
||||
|
||||
@@ -55,9 +55,9 @@ export class DashScopeImageAdapter extends AbstractImageProviderAdapter {
|
||||
getModels(): ImageModel[] {
|
||||
return [
|
||||
{
|
||||
id: 'qwen-image-2.0',
|
||||
name: 'Qwen Image 2.0',
|
||||
description: 'Unified Qwen image model for text-to-image generation and image editing',
|
||||
id: 'qwen-image-3.0-pro',
|
||||
name: 'Qwen Image 3.0 Pro',
|
||||
description: 'Flagship unified Qwen image model for text-to-image generation and image editing',
|
||||
providerId: 'dashscope',
|
||||
capabilities: {
|
||||
text2image: true,
|
||||
@@ -71,16 +71,15 @@ export class DashScopeImageAdapter extends AbstractImageProviderAdapter {
|
||||
watermark: false
|
||||
}
|
||||
},
|
||||
// Qwen-Image 文生图模型
|
||||
{
|
||||
id: 'qwen-image',
|
||||
name: 'Qwen Image',
|
||||
description: 'Qwen text-to-image model with strong text rendering, multi-line layout, and paragraph-level text generation support',
|
||||
id: 'qwen-image-3.0',
|
||||
name: 'Qwen Image 3.0',
|
||||
description: 'Fast unified Qwen image model for text-to-image generation and image editing',
|
||||
providerId: 'dashscope',
|
||||
capabilities: {
|
||||
text2image: true,
|
||||
image2image: false,
|
||||
multiImage: false
|
||||
image2image: true,
|
||||
multiImage: true
|
||||
},
|
||||
parameterDefinitions: this.getQwenImageParameterDefinitions(),
|
||||
defaultParameterValues: {
|
||||
@@ -238,7 +237,7 @@ export class DashScopeImageAdapter extends AbstractImageProviderAdapter {
|
||||
}
|
||||
|
||||
private isQwenImageUnifiedModel(modelId: string): boolean {
|
||||
return modelId === 'qwen-image-2.0'
|
||||
return modelId === 'qwen-image-2.0' || modelId.startsWith('qwen-image-3.0')
|
||||
}
|
||||
|
||||
protected async doGenerate(request: ImageRequest, config: ImageModelConfig): Promise<ImageResult> {
|
||||
|
||||
@@ -13,7 +13,7 @@ import { IMAGE_ERROR_CODES } from '../../../constants/error-codes'
|
||||
|
||||
export class GrokImageAdapter extends AbstractImageProviderAdapter {
|
||||
private static readonly FORCED_SINGLE_OUTPUT_PARAM_KEYS = ['n', 'batch_size', 'count']
|
||||
private static readonly MAX_INPUT_IMAGES = 3
|
||||
private static readonly MAX_INPUT_IMAGES = 5
|
||||
|
||||
protected normalizeBaseUrl(base: string): string {
|
||||
const trimmed = base.replace(/\/$/, '')
|
||||
@@ -43,17 +43,17 @@ export class GrokImageAdapter extends AbstractImageProviderAdapter {
|
||||
getModels(): ImageModel[] {
|
||||
return [
|
||||
{
|
||||
id: 'grok-imagine-image-quality',
|
||||
name: 'Grok Imagine Image Quality',
|
||||
description: 'xAI Grok Imagine image generation and JSON-based image editing model',
|
||||
id: 'grok-imagine-image-2.0',
|
||||
name: 'Grok Imagine Image 2.0',
|
||||
description: 'xAI Grok Imagine 2.0 image generation and JSON-based image editing model',
|
||||
providerId: 'grok',
|
||||
capabilities: {
|
||||
text2image: true,
|
||||
image2image: true,
|
||||
multiImage: true
|
||||
},
|
||||
parameterDefinitions: this.getParameterDefinitions('grok-imagine-image-quality'),
|
||||
defaultParameterValues: this.getDefaultParameterValues('grok-imagine-image-quality')
|
||||
parameterDefinitions: this.getParameterDefinitions('grok-imagine-image-2.0'),
|
||||
defaultParameterValues: this.getDefaultParameterValues('grok-imagine-image-2.0')
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -138,7 +138,7 @@ export class GrokImageAdapter extends AbstractImageProviderAdapter {
|
||||
descriptionKey: 'image.params.aspect_ratio.description',
|
||||
type: 'string',
|
||||
defaultValue: 'auto',
|
||||
allowedValues: ['auto', '1:1', '16:9', '9:16', '4:3', '3:4', '3:2', '2:3', '2:1', '1:2', '19.5:9', '9:19.5', '20:9', '9:20']
|
||||
allowedValues: ['auto', '1:1', '16:9', '9:16', '4:3', '3:4', '3:2', '2:3', '2:1', '1:2', '19.5:9', '9:19.5', '20:9', '9:20', '21:9', '5:2']
|
||||
},
|
||||
{
|
||||
name: 'resolution',
|
||||
@@ -147,6 +147,14 @@ export class GrokImageAdapter extends AbstractImageProviderAdapter {
|
||||
type: 'string',
|
||||
defaultValue: '1k',
|
||||
allowedValues: ['1k', '2k']
|
||||
},
|
||||
{
|
||||
name: 'quality',
|
||||
labelKey: 'image.params.quality.label',
|
||||
descriptionKey: 'image.params.quality.description',
|
||||
type: 'string',
|
||||
defaultValue: 'auto',
|
||||
allowedValues: ['auto', 'low', 'medium']
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -155,6 +163,7 @@ export class GrokImageAdapter extends AbstractImageProviderAdapter {
|
||||
return {
|
||||
aspect_ratio: 'auto',
|
||||
resolution: '1k',
|
||||
quality: 'auto',
|
||||
response_format: 'b64_json'
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ import type {
|
||||
import { ImageError } from '../errors'
|
||||
import { IMAGE_ERROR_CODES } from '../../../constants/error-codes'
|
||||
|
||||
const GPT_IMAGE_2_SIZE_VALUES = [
|
||||
const GPT_IMAGE_SIZE_VALUES = [
|
||||
'1024x1024',
|
||||
'1536x1024',
|
||||
'1024x1536',
|
||||
@@ -21,7 +21,7 @@ const GPT_IMAGE_2_SIZE_VALUES = [
|
||||
'auto'
|
||||
] as const
|
||||
|
||||
const GPT_IMAGE_2_BACKGROUND_VALUES = ['auto', 'opaque'] as const
|
||||
const GPT_IMAGE_BACKGROUND_VALUES = ['auto', 'opaque'] as const
|
||||
|
||||
export class OpenAIImageAdapter extends AbstractImageProviderAdapter {
|
||||
private static readonly FORCED_SINGLE_OUTPUT_PARAM_KEYS = ['n', 'batch_size', 'outputMimeType', 'response_format']
|
||||
@@ -53,9 +53,9 @@ export class OpenAIImageAdapter extends AbstractImageProviderAdapter {
|
||||
getModels(): ImageModel[] {
|
||||
return [
|
||||
{
|
||||
id: 'gpt-image-2',
|
||||
name: 'GPT Image 2',
|
||||
description: 'OpenAI GPT Image 2 image generation model with text-to-image and image editing support',
|
||||
id: 'gpt-image-2.5-flare',
|
||||
name: 'GPT Image 2.5 Flare',
|
||||
description: 'OpenAI fast, high-quality image generation and editing model for everyday use',
|
||||
providerId: 'openai',
|
||||
capabilities: {
|
||||
text2image: true,
|
||||
@@ -69,7 +69,7 @@ export class OpenAIImageAdapter extends AbstractImageProviderAdapter {
|
||||
descriptionKey: 'image.params.size.description',
|
||||
type: 'string',
|
||||
defaultValue: '1024x1024',
|
||||
allowedValues: [...GPT_IMAGE_2_SIZE_VALUES]
|
||||
allowedValues: [...GPT_IMAGE_SIZE_VALUES]
|
||||
},
|
||||
{
|
||||
name: 'quality',
|
||||
@@ -77,7 +77,7 @@ export class OpenAIImageAdapter extends AbstractImageProviderAdapter {
|
||||
descriptionKey: 'image.params.quality.description',
|
||||
type: 'string',
|
||||
defaultValue: 'auto',
|
||||
allowedValues: ['auto', 'high', 'medium', 'low']
|
||||
allowedValues: ['auto', 'max', 'xhigh', 'high', 'medium', 'low']
|
||||
},
|
||||
{
|
||||
name: 'background',
|
||||
@@ -85,7 +85,7 @@ export class OpenAIImageAdapter extends AbstractImageProviderAdapter {
|
||||
descriptionKey: 'image.params.background.description',
|
||||
type: 'string',
|
||||
defaultValue: 'auto',
|
||||
allowedValues: [...GPT_IMAGE_2_BACKGROUND_VALUES]
|
||||
allowedValues: [...GPT_IMAGE_BACKGROUND_VALUES]
|
||||
}
|
||||
],
|
||||
defaultParameterValues: {
|
||||
@@ -158,7 +158,7 @@ export class OpenAIImageAdapter extends AbstractImageProviderAdapter {
|
||||
}
|
||||
|
||||
protected getParameterDefinitions(_modelId: string): readonly ImageParameterDefinition[] {
|
||||
// GPT Image 2 使用统一的参数定义,n参数固定为1不暴露给用户
|
||||
// GPT Image 2.x 使用统一的参数定义,n参数固定为1不暴露给用户
|
||||
return [
|
||||
{
|
||||
name: 'size',
|
||||
@@ -166,7 +166,7 @@ export class OpenAIImageAdapter extends AbstractImageProviderAdapter {
|
||||
descriptionKey: 'image.params.size.description',
|
||||
type: 'string',
|
||||
defaultValue: '1024x1024',
|
||||
allowedValues: [...GPT_IMAGE_2_SIZE_VALUES]
|
||||
allowedValues: [...GPT_IMAGE_SIZE_VALUES]
|
||||
},
|
||||
{
|
||||
name: 'quality',
|
||||
@@ -174,7 +174,7 @@ export class OpenAIImageAdapter extends AbstractImageProviderAdapter {
|
||||
descriptionKey: 'image.params.quality.description',
|
||||
type: 'string',
|
||||
defaultValue: 'auto',
|
||||
allowedValues: ['auto', 'high', 'medium', 'low']
|
||||
allowedValues: ['auto', 'max', 'xhigh', 'high', 'medium', 'low']
|
||||
},
|
||||
{
|
||||
name: 'background',
|
||||
@@ -182,7 +182,7 @@ export class OpenAIImageAdapter extends AbstractImageProviderAdapter {
|
||||
descriptionKey: 'image.params.background.description',
|
||||
type: 'string',
|
||||
defaultValue: 'auto',
|
||||
allowedValues: [...GPT_IMAGE_2_BACKGROUND_VALUES]
|
||||
allowedValues: [...GPT_IMAGE_BACKGROUND_VALUES]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -41,9 +41,9 @@ export class OpenRouterImageAdapter extends AbstractImageProviderAdapter {
|
||||
getModels(): ImageModel[] {
|
||||
return [
|
||||
{
|
||||
id: 'google/gemini-2.5-flash-image',
|
||||
name: 'Gemini 2.5 Flash Image (Nano Banana)',
|
||||
description: 'Google Gemini 2.5 Flash image model via OpenRouter with text-to-image, image editing, and multi-turn editing support',
|
||||
id: 'google/gemini-3.1-flash-image',
|
||||
name: 'Gemini 3.1 Flash Image (Nano Banana 2)',
|
||||
description: 'Google Gemini 3.1 Flash image model via OpenRouter with text-to-image, image editing, and multi-turn editing support',
|
||||
providerId: 'openrouter',
|
||||
capabilities: {
|
||||
text2image: true,
|
||||
|
||||
@@ -24,8 +24,8 @@ const DASHSCOPE_STATIC_MODELS: ModelOverride[] = [
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'qwen3.6-flash',
|
||||
name: 'Qwen3.6 Flash',
|
||||
id: 'qwen3.8-flash',
|
||||
name: 'Qwen3.8 Flash',
|
||||
description: 'Fast Qwen model for low-latency general-purpose workloads',
|
||||
capabilities: {
|
||||
supportsTools: true,
|
||||
@@ -34,13 +34,13 @@ const DASHSCOPE_STATIC_MODELS: ModelOverride[] = [
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'qwen3.5-27b',
|
||||
name: 'Qwen3.5-27B',
|
||||
description: 'Qwen3.5 27B model for DashScope text and vision understanding scenarios',
|
||||
id: 'qwen3.8-max',
|
||||
name: 'Qwen3.8 Max',
|
||||
description: 'Latest Qwen flagship model for complex reasoning and agentic workloads',
|
||||
capabilities: {
|
||||
supportsTools: true,
|
||||
supportsReasoning: true,
|
||||
maxContextLength: 131072
|
||||
maxContextLength: 1000000
|
||||
}
|
||||
},
|
||||
{
|
||||
|
||||
@@ -33,8 +33,8 @@ interface ModelOverride {
|
||||
*/
|
||||
const GEMINI_STATIC_MODELS: ModelOverride[] = [
|
||||
{
|
||||
id: 'gemini-3.6-flash',
|
||||
name: 'Gemini 3.6 Flash',
|
||||
id: 'gemini-3.8-flash',
|
||||
name: 'Gemini 3.8 Flash',
|
||||
description: 'Latest Gemini Flash model for fast multimodal reasoning and agentic workloads',
|
||||
capabilities: {
|
||||
supportsTools: true,
|
||||
@@ -292,7 +292,8 @@ export class GeminiAdapter extends AbstractTextProviderAdapter {
|
||||
]
|
||||
|
||||
if (this.isGemini3Model(modelId)) {
|
||||
const allowedValues = modelId.replace(/^models\//, '').startsWith('gemini-3.1-pro')
|
||||
const normalizedModelId = modelId.replace(/^models\//, '')
|
||||
const allowedValues = normalizedModelId.startsWith('gemini-3.1-pro') || normalizedModelId.startsWith('gemini-3.8-')
|
||||
? ['low', 'medium', 'high']
|
||||
: ['minimal', 'low', 'medium', 'high']
|
||||
return [
|
||||
@@ -434,7 +435,7 @@ export class GeminiAdapter extends AbstractTextProviderAdapter {
|
||||
|
||||
private usesDeprecatedSamplingParameters(modelId: string): boolean {
|
||||
const normalizedModelId = modelId.replace(/^models\//, '')
|
||||
return normalizedModelId.startsWith('gemini-3.5-') || normalizedModelId.startsWith('gemini-3.6-')
|
||||
return normalizedModelId.startsWith('gemini-3.5-') || normalizedModelId.startsWith('gemini-3.6-') || normalizedModelId.startsWith('gemini-3.8-')
|
||||
}
|
||||
|
||||
private isGemini3Model(modelId: string): boolean {
|
||||
|
||||
@@ -16,9 +16,9 @@ interface ModelOverride {
|
||||
|
||||
const GROK_STATIC_MODELS: ModelOverride[] = [
|
||||
{
|
||||
id: 'grok-4.5',
|
||||
name: 'Grok 4.5',
|
||||
description: 'xAI Grok 4.5 reasoning model via OpenAI-compatible Chat Completions API',
|
||||
id: 'grok-4.6',
|
||||
name: 'Grok 4.6',
|
||||
description: 'xAI Grok 4.6 reasoning model via OpenAI-compatible Chat Completions API',
|
||||
capabilities: {
|
||||
supportsTools: true,
|
||||
supportsReasoning: true,
|
||||
@@ -30,7 +30,7 @@ const GROK_STATIC_MODELS: ModelOverride[] = [
|
||||
}
|
||||
]
|
||||
|
||||
type GrokReasoningEffort = 'low' | 'medium' | 'high'
|
||||
type GrokReasoningEffort = 'low' | 'medium' | 'high' | 'xhigh'
|
||||
|
||||
export class GrokAdapter extends OpenAIAdapter {
|
||||
public getProvider(): TextProvider {
|
||||
@@ -125,11 +125,11 @@ export class GrokAdapter extends OpenAIAdapter {
|
||||
name: 'reasoning_effort',
|
||||
labelKey: 'params.reasoning_effort.label',
|
||||
descriptionKey: 'params.reasoning_effort.description',
|
||||
description: 'Reasoning effort for Grok 4.5.',
|
||||
description: 'Reasoning effort for Grok 4.6.',
|
||||
type: 'string',
|
||||
defaultValue: 'high',
|
||||
default: 'high',
|
||||
allowedValues: ['low', 'medium', 'high']
|
||||
allowedValues: ['low', 'medium', 'high', 'xhigh']
|
||||
},
|
||||
{
|
||||
name: 'temperature',
|
||||
@@ -215,7 +215,7 @@ export class GrokAdapter extends OpenAIAdapter {
|
||||
}
|
||||
|
||||
private normalizeReasoningEffort(value: unknown): GrokReasoningEffort | undefined {
|
||||
return value === 'low' || value === 'medium' || value === 'high'
|
||||
return value === 'low' || value === 'medium' || value === 'high' || value === 'xhigh'
|
||||
? value
|
||||
: undefined
|
||||
}
|
||||
|
||||
@@ -40,16 +40,16 @@ const OPENAI_STATIC_MODELS: ModelOverride[] = [
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'gpt-5.6-sol',
|
||||
name: 'GPT-5.6 Sol',
|
||||
description: 'Most capable GPT-5.6 model for complex coding and agentic tasks',
|
||||
id: 'gpt-6-astra',
|
||||
name: 'GPT-6 Astra',
|
||||
description: 'Most capable OpenAI model for the hardest end-to-end work',
|
||||
capabilities: {
|
||||
supportsTools: true,
|
||||
supportsReasoning: true,
|
||||
maxContextLength: 1050000
|
||||
},
|
||||
defaultParameterValues: {
|
||||
reasoning_effort: 'none'
|
||||
reasoning_effort: 'low'
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -194,17 +194,20 @@ export class OpenAIAdapter extends AbstractTextProviderAdapter {
|
||||
* 获取参数定义
|
||||
* 基于 OpenAI 官方文档: https://platform.openai.com/docs/api-reference/chat/create
|
||||
*/
|
||||
protected getParameterDefinitions(_modelId: string): readonly ParameterDefinition[] {
|
||||
protected getParameterDefinitions(modelId: string): readonly ParameterDefinition[] {
|
||||
const isAstra = modelId === 'gpt-6-astra'
|
||||
return [
|
||||
{
|
||||
name: 'reasoning_effort',
|
||||
labelKey: 'params.reasoning_effort.label',
|
||||
descriptionKey: 'params.reasoning_effort.description',
|
||||
description: 'Reasoning effort for GPT-5.6 models.',
|
||||
description: 'Reasoning effort for current OpenAI models.',
|
||||
type: 'string',
|
||||
defaultValue: 'none',
|
||||
default: 'none',
|
||||
allowedValues: ['none', 'low', 'medium', 'high', 'xhigh', 'max']
|
||||
defaultValue: isAstra ? 'low' : 'none',
|
||||
default: isAstra ? 'low' : 'none',
|
||||
allowedValues: isAstra
|
||||
? ['low', 'medium', 'high', 'xhigh', 'max']
|
||||
: ['none', 'low', 'medium', 'high', 'xhigh', 'max']
|
||||
},
|
||||
{
|
||||
name: 'temperature',
|
||||
|
||||
@@ -11,13 +11,13 @@ interface ModelOverride {
|
||||
|
||||
const OPENROUTER_STATIC_MODELS: ModelOverride[] = [
|
||||
{
|
||||
id: 'google/gemma-3-27b-it:free',
|
||||
name: 'Gemma 3 27B IT (Free)',
|
||||
description: 'Free Google Gemma 3 27B model served through OpenRouter',
|
||||
id: 'openrouter/free',
|
||||
name: 'OpenRouter Free Models Router',
|
||||
description: 'OpenRouter router that selects a compatible free model for each request',
|
||||
capabilities: {
|
||||
supportsTools: true,
|
||||
supportsReasoning: false,
|
||||
maxContextLength: 96000
|
||||
maxContextLength: 200000
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { TextModel, TextProvider } from '../types'
|
||||
import type { ParameterDefinition, TextModel, TextProvider } from '../types'
|
||||
import { OpenAIAdapter } from './openai-adapter'
|
||||
|
||||
interface ModelOverride {
|
||||
@@ -11,9 +11,9 @@ interface ModelOverride {
|
||||
|
||||
const ZHIPU_STATIC_MODELS: ModelOverride[] = [
|
||||
{
|
||||
id: 'glm-5.2',
|
||||
name: 'GLM-5.2',
|
||||
description: 'Latest GLM flagship model for reasoning, coding, and agentic tasks',
|
||||
id: 'glm-5.3-flash',
|
||||
name: 'GLM-5.3-Flash',
|
||||
description: 'Latest efficient multimodal GLM model for reasoning, coding, and agentic tasks',
|
||||
capabilities: {
|
||||
supportsTools: true,
|
||||
supportsReasoning: true,
|
||||
@@ -21,23 +21,23 @@ const ZHIPU_STATIC_MODELS: ModelOverride[] = [
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'glm-4.7',
|
||||
name: 'GLM-4.7',
|
||||
description: 'GLM-4.7 是最新的旗舰模型系列,专为智能体应用打造的基础模型',
|
||||
id: 'glm-5.3',
|
||||
name: 'GLM-5.3',
|
||||
description: 'Latest GLM flagship model for complex reasoning, coding, and agentic tasks',
|
||||
capabilities: {
|
||||
supportsTools: true,
|
||||
supportsReasoning: true,
|
||||
maxContextLength: 128000
|
||||
maxContextLength: 1000000
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'glm-4.6',
|
||||
name: 'GLM-4.6',
|
||||
description: 'GLM-4.6 是最新的旗舰模型系列,专为智能体应用打造的基础模型',
|
||||
id: 'glm-5.2',
|
||||
name: 'GLM-5.2',
|
||||
description: 'Previous GLM flagship model retained for compatibility',
|
||||
capabilities: {
|
||||
supportsTools: true,
|
||||
supportsReasoning: true,
|
||||
maxContextLength: 128000
|
||||
maxContextLength: 1000000
|
||||
}
|
||||
}
|
||||
]
|
||||
@@ -84,4 +84,21 @@ export class ZhipuAdapter extends OpenAIAdapter {
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
protected getParameterDefinitions(modelId: string): readonly ParameterDefinition[] {
|
||||
return super.getParameterDefinitions(modelId).map((definition) => {
|
||||
if (definition.name !== 'reasoning_effort' || !/^glm-5\.(?:2|3)/.test(modelId)) {
|
||||
return definition
|
||||
}
|
||||
|
||||
const allowedValues = modelId === 'glm-5.2' ? ['high', 'max'] : ['low', 'high', 'max']
|
||||
return {
|
||||
...definition,
|
||||
description: 'Reasoning effort for current GLM models.',
|
||||
defaultValue: 'max',
|
||||
default: 'max',
|
||||
allowedValues
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -258,11 +258,11 @@ export class ModelManager implements IModelManager {
|
||||
): TextModelConfig {
|
||||
const legacyDefaultIds: Record<string, readonly string[]> = {
|
||||
openai: ['gpt-5-mini'],
|
||||
gemini: ['gemini-2.5-flash'],
|
||||
gemini: ['gemini-2.5-flash', 'gemini-3.6-flash'],
|
||||
anthropic: ['claude-opus-4-20250514', 'claude-sonnet-4-20250514'],
|
||||
zhipu: ['glm-4.7'],
|
||||
zhipu: ['glm-4.7', 'glm-5.2'],
|
||||
dashscope: ['qwen3.5-27b'],
|
||||
grok: ['grok-4.3']
|
||||
grok: ['grok-4.3', 'grok-4.5']
|
||||
};
|
||||
const currentModelId = config.modelId || config.modelMeta?.id;
|
||||
if (!currentModelId || !legacyDefaultIds[key]?.includes(currentModelId)) {
|
||||
|
||||
@@ -28,7 +28,7 @@ function pickRealVisionProvider(): RealVisionProvider | null {
|
||||
|
||||
if (dashscopeApiKey) {
|
||||
const adapter = new DashScopeAdapter()
|
||||
const modelId = process.env.REAL_QWEN_VISION_MODEL || 'qwen3.5-27b'
|
||||
const modelId = process.env.REAL_QWEN_VISION_MODEL || 'qwen3.8-flash'
|
||||
|
||||
return {
|
||||
label: `dashscope/${modelId}`,
|
||||
|
||||
@@ -104,7 +104,7 @@ describe('DashScopeImageAdapter', () => {
|
||||
|
||||
const [, init] = vi.mocked(fetch).mock.calls[0]!
|
||||
const body = JSON.parse(String(init?.body))
|
||||
expect(body.model).toBe('qwen-image-2.0')
|
||||
expect(body.model).toBe('qwen-image-3.0-pro')
|
||||
expect(body.input.messages[0].content).toEqual([
|
||||
{ image: 'data:image/png;base64,aGVsbG8=' },
|
||||
{ image: 'data:image/jpeg;base64,d29ybGQ=' },
|
||||
|
||||
@@ -38,7 +38,9 @@ describe('GrokImageAdapter', () => {
|
||||
it('should expose current non-retired Grok Imagine model', () => {
|
||||
const models = adapter.getModels()
|
||||
|
||||
expect(models.map(model => model.id)).toEqual(['grok-imagine-image-quality'])
|
||||
expect(models.map(model => model.id)).toEqual(['grok-imagine-image-2.0'])
|
||||
expect(models[0].parameterDefinitions.find(parameter => parameter.name === 'quality')?.allowedValues)
|
||||
.toEqual(['auto', 'low', 'medium'])
|
||||
expect(models[0].providerId).toBe('grok')
|
||||
expect(models[0].capabilities).toEqual({
|
||||
text2image: true,
|
||||
@@ -83,7 +85,7 @@ describe('GrokImageAdapter', () => {
|
||||
const payload = JSON.parse(options.body)
|
||||
expect(payload).toEqual(
|
||||
expect.objectContaining({
|
||||
model: 'grok-imagine-image-quality',
|
||||
model: 'grok-imagine-image-2.0',
|
||||
prompt: 'a city at dawn',
|
||||
response_format: 'b64_json',
|
||||
n: 1
|
||||
@@ -118,7 +120,7 @@ describe('GrokImageAdapter', () => {
|
||||
const payload = JSON.parse(options.body)
|
||||
expect(payload).toEqual(
|
||||
expect.objectContaining({
|
||||
model: 'grok-imagine-image-quality',
|
||||
model: 'grok-imagine-image-2.0',
|
||||
prompt: 'make it cinematic',
|
||||
response_format: 'b64_json',
|
||||
n: 1,
|
||||
@@ -130,11 +132,11 @@ describe('GrokImageAdapter', () => {
|
||||
)
|
||||
})
|
||||
|
||||
it('should reject more than three input images before calling xAI', async () => {
|
||||
it('should reject more than five input images before calling xAI', async () => {
|
||||
const request: ImageRequest = {
|
||||
prompt: 'combine references',
|
||||
configId: config.id,
|
||||
inputImages: Array.from({ length: 4 }, () => ({
|
||||
inputImages: Array.from({ length: 6 }, () => ({
|
||||
b64: 'cmVmZXJlbmNl',
|
||||
mimeType: 'image/png'
|
||||
})),
|
||||
@@ -146,8 +148,8 @@ describe('GrokImageAdapter', () => {
|
||||
await expect(adapter.generate(request, config)).rejects.toMatchObject({
|
||||
code: IMAGE_ERROR_CODES.INPUT_IMAGE_TOO_MANY,
|
||||
params: {
|
||||
maxCount: 3,
|
||||
actualCount: 4
|
||||
maxCount: 5,
|
||||
actualCount: 6
|
||||
}
|
||||
})
|
||||
expect(global.fetch).not.toHaveBeenCalled()
|
||||
|
||||
@@ -71,8 +71,8 @@ describe('default image models', () => {
|
||||
|
||||
expect(openaiConfig).toBeDefined()
|
||||
expect(openaiConfig.providerId).toBe('openai')
|
||||
expect(openaiConfig.modelId).toBe('gpt-image-2')
|
||||
expect(openaiConfig.model.id).toBe('gpt-image-2')
|
||||
expect(openaiConfig.modelId).toBe('gpt-image-2.5-flare')
|
||||
expect(openaiConfig.model.id).toBe('gpt-image-2.5-flare')
|
||||
expect(openaiConfig.model.capabilities.multiImage).toBe(true)
|
||||
expect(openaiConfig.connectionConfig?.apiKey).toBe('openai-key')
|
||||
expect(openaiConfig.enabled).toBe(true)
|
||||
@@ -158,7 +158,7 @@ describe('default image models', () => {
|
||||
|
||||
expect(grokConfig).toBeDefined()
|
||||
expect(grokConfig.providerId).toBe('grok')
|
||||
expect(grokConfig.modelId).toBe('grok-imagine-image-quality')
|
||||
expect(grokConfig.modelId).toBe('grok-imagine-image-2.0')
|
||||
expect(grokConfig.model.capabilities.text2image).toBe(true)
|
||||
expect(grokConfig.model.capabilities.image2image).toBe(true)
|
||||
expect(grokConfig.model.capabilities.multiImage).toBe(true)
|
||||
|
||||
@@ -101,36 +101,64 @@ describe('ImageModelManager initialization behavior', () => {
|
||||
it('should migrate exact legacy builtin image model ids', async () => {
|
||||
await modelManager.ensureInitialized()
|
||||
const defaults = {
|
||||
openrouter: await modelManager.getConfig('image-openrouter-nanobanana'),
|
||||
gemini: await modelManager.getConfig('image-gemini-nanobanana'),
|
||||
openai: await modelManager.getConfig('image-openai-gpt'),
|
||||
dashscope: await modelManager.getConfig('image-dashscope'),
|
||||
seedream: await modelManager.getConfig('image-seedream-50-lite')
|
||||
seedream: await modelManager.getConfig('image-seedream-50-lite'),
|
||||
grok: await modelManager.getConfig('image-grok-imagine')
|
||||
}
|
||||
expect(defaults.gemini && defaults.dashscope && defaults.seedream).toBeTruthy()
|
||||
expect(
|
||||
defaults.openrouter
|
||||
&& defaults.gemini
|
||||
&& defaults.openai
|
||||
&& defaults.dashscope
|
||||
&& defaults.seedream
|
||||
&& defaults.grok
|
||||
).toBeTruthy()
|
||||
|
||||
await storageProvider.setItem(CORE_SERVICE_KEYS.IMAGE_MODELS, JSON.stringify({
|
||||
'image-openrouter-nanobanana': {
|
||||
...defaults.openrouter,
|
||||
modelId: 'google/gemini-2.5-flash-image',
|
||||
model: { ...defaults.openrouter!.model, id: 'google/gemini-2.5-flash-image' }
|
||||
},
|
||||
'image-gemini-nanobanana': {
|
||||
...defaults.gemini,
|
||||
modelId: 'gemini-2.5-flash-image',
|
||||
model: { ...defaults.gemini!.model, id: 'gemini-2.5-flash-image' }
|
||||
},
|
||||
'image-openai-gpt': {
|
||||
...defaults.openai,
|
||||
modelId: 'gpt-image-2',
|
||||
model: { ...defaults.openai!.model, id: 'gpt-image-2' }
|
||||
},
|
||||
'image-dashscope': {
|
||||
...defaults.dashscope,
|
||||
modelId: 'qwen-image',
|
||||
model: { ...defaults.dashscope!.model, id: 'qwen-image' }
|
||||
modelId: 'qwen-image-2.0',
|
||||
model: { ...defaults.dashscope!.model, id: 'qwen-image-2.0' }
|
||||
},
|
||||
'image-seedream-50-lite': {
|
||||
...defaults.seedream,
|
||||
modelId: 'doubao-seedream-5-0-260128',
|
||||
model: { ...defaults.seedream!.model, id: 'doubao-seedream-5-0-260128' }
|
||||
},
|
||||
'image-grok-imagine': {
|
||||
...defaults.grok,
|
||||
modelId: 'grok-imagine-image-quality',
|
||||
model: { ...defaults.grok!.model, id: 'grok-imagine-image-quality' }
|
||||
}
|
||||
}))
|
||||
|
||||
const reloadedManager = new ImageModelManager(storageProvider, new ImageAdapterRegistry())
|
||||
await reloadedManager.ensureInitialized()
|
||||
|
||||
expect((await reloadedManager.getConfig('image-openrouter-nanobanana'))?.modelId).toBe('google/gemini-3.1-flash-image')
|
||||
expect((await reloadedManager.getConfig('image-gemini-nanobanana'))?.modelId).toBe('gemini-3.1-flash-image')
|
||||
expect((await reloadedManager.getConfig('image-dashscope'))?.modelId).toBe('qwen-image-2.0')
|
||||
expect((await reloadedManager.getConfig('image-openai-gpt'))?.modelId).toBe('gpt-image-2.5-flare')
|
||||
expect((await reloadedManager.getConfig('image-dashscope'))?.modelId).toBe('qwen-image-3.0-pro')
|
||||
expect((await reloadedManager.getConfig('image-seedream-50-lite'))?.modelId).toBe('doubao-seedream-5-0-lite-260128')
|
||||
expect((await reloadedManager.getConfig('image-grok-imagine'))?.modelId).toBe('grok-imagine-image-2.0')
|
||||
})
|
||||
|
||||
it('should refresh embedded provider and model metadata when providerId/modelId are updated directly', async () => {
|
||||
|
||||
@@ -34,15 +34,15 @@ describe('OpenAIImageAdapter', () => {
|
||||
})
|
||||
|
||||
describe('Static Models', () => {
|
||||
test('should return GPT Image 2 as the static default model', () => {
|
||||
test('should return GPT Image 2.5 Flare as the static default model', () => {
|
||||
const models = adapter.getModels()
|
||||
|
||||
expect(Array.isArray(models)).toBe(true)
|
||||
expect(models.map(model => model.id)).toEqual(['gpt-image-2'])
|
||||
expect(models.map(model => model.id)).toEqual(['gpt-image-2.5-flare'])
|
||||
|
||||
const imageModel = models[0]
|
||||
expect(imageModel).toMatchObject({
|
||||
id: 'gpt-image-2',
|
||||
id: 'gpt-image-2.5-flare',
|
||||
name: expect.any(String),
|
||||
providerId: 'openai',
|
||||
capabilities: {
|
||||
@@ -54,9 +54,9 @@ describe('OpenAIImageAdapter', () => {
|
||||
})
|
||||
})
|
||||
|
||||
test('should expose supported GPT Image 2 parameters', () => {
|
||||
test('should expose supported GPT Image 2.5 parameters', () => {
|
||||
const models = adapter.getModels()
|
||||
const model = models.find(m => m.id === 'gpt-image-2')
|
||||
const model = models.find(m => m.id === 'gpt-image-2.5-flare')
|
||||
|
||||
expect(model?.parameterDefinitions).toBeDefined()
|
||||
const qualityParam = model?.parameterDefinitions?.find(p => p.name === 'quality')
|
||||
@@ -65,7 +65,7 @@ describe('OpenAIImageAdapter', () => {
|
||||
|
||||
expect(qualityParam).toBeDefined()
|
||||
expect(qualityParam?.type).toBe('string')
|
||||
expect(qualityParam?.allowedValues).toEqual(expect.arrayContaining(['auto', 'high', 'medium', 'low']))
|
||||
expect(qualityParam?.allowedValues).toEqual(['auto', 'max', 'xhigh', 'high', 'medium', 'low'])
|
||||
|
||||
expect(sizeParam).toBeDefined()
|
||||
expect(sizeParam?.allowedValues).toEqual([
|
||||
@@ -91,7 +91,7 @@ describe('OpenAIImageAdapter', () => {
|
||||
data: [
|
||||
{ id: 'gpt-5.1' },
|
||||
{ id: 'third-party-text-model', name: 'Text Model' },
|
||||
{ id: 'gpt-image-2', name: 'GPT Image 2' },
|
||||
{ id: 'gpt-image-2.5-flare', name: 'GPT Image 2.5 Flare' },
|
||||
{ id: 'vendor/custom-image-fast', name: 'Custom Image Fast' }
|
||||
]
|
||||
})
|
||||
@@ -103,7 +103,7 @@ describe('OpenAIImageAdapter', () => {
|
||||
})
|
||||
|
||||
expect(models.map(model => model.id)).toEqual([
|
||||
'gpt-image-2',
|
||||
'gpt-image-2.5-flare',
|
||||
'vendor/custom-image-fast',
|
||||
'gpt-5.1',
|
||||
'third-party-text-model'
|
||||
@@ -120,7 +120,7 @@ describe('OpenAIImageAdapter', () => {
|
||||
)
|
||||
})
|
||||
|
||||
test('should fall back to static GPT Image 2 model when model fetch fails', async () => {
|
||||
test('should fall back to static GPT Image 2.5 Flare model when model fetch fails', async () => {
|
||||
global.fetch = vi.fn().mockResolvedValue({
|
||||
ok: false,
|
||||
status: 503,
|
||||
@@ -129,7 +129,7 @@ describe('OpenAIImageAdapter', () => {
|
||||
|
||||
const models = await adapter.getModelsAsync({ apiKey: 'test-api-key' })
|
||||
|
||||
expect(models.map(model => model.id)).toEqual(['gpt-image-2'])
|
||||
expect(models.map(model => model.id)).toEqual(['gpt-image-2.5-flare'])
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
@@ -41,8 +41,8 @@ describe('OpenRouterImageAdapter', () => {
|
||||
const models = adapter.getModels()
|
||||
|
||||
expect(models.length).toBeGreaterThan(0)
|
||||
expect(models[0].id).toBe('google/gemini-2.5-flash-image')
|
||||
expect(models[0].name).toBe('Gemini 2.5 Flash Image (Nano Banana)')
|
||||
expect(models[0].id).toBe('google/gemini-3.1-flash-image')
|
||||
expect(models[0].name).toBe('Gemini 3.1 Flash Image (Nano Banana 2)')
|
||||
expect(models[0].providerId).toBe('openrouter')
|
||||
})
|
||||
|
||||
|
||||
@@ -75,7 +75,7 @@ describe('GeminiAdapter', () => {
|
||||
expect(models.length).toBeGreaterThan(0);
|
||||
|
||||
expect(models.map(model => model.id)).toEqual([
|
||||
'gemini-3.6-flash',
|
||||
'gemini-3.8-flash',
|
||||
'gemini-3.5-flash-lite',
|
||||
'gemini-3.1-pro-preview'
|
||||
]);
|
||||
@@ -100,7 +100,7 @@ describe('GeminiAdapter', () => {
|
||||
|
||||
const paramNames = model.parameterDefinitions.map(p => p.name);
|
||||
|
||||
// Gemini 3.5/3.6 已弃用这些采样参数
|
||||
// Gemini 3.5/3.8 已弃用这些采样参数
|
||||
expect(paramNames).not.toContain('temperature');
|
||||
expect(paramNames).not.toContain('topP');
|
||||
expect(paramNames).not.toContain('topK');
|
||||
@@ -112,7 +112,7 @@ describe('GeminiAdapter', () => {
|
||||
expect(paramNames).toContain('includeThoughts');
|
||||
|
||||
const thinkingLevel = model.parameterDefinitions.find(p => p.name === 'thinkingLevel');
|
||||
expect(thinkingLevel?.allowedValues).toEqual(['minimal', 'low', 'medium', 'high']);
|
||||
expect(thinkingLevel?.allowedValues).toEqual(['low', 'medium', 'high']);
|
||||
|
||||
const includeThoughts = model.parameterDefinitions.find(p => p.name === 'includeThoughts');
|
||||
expect(includeThoughts).toBeDefined();
|
||||
|
||||
@@ -67,14 +67,16 @@ describe('GrokAdapter', () => {
|
||||
expect(provider.connectionSchema?.required).toContain('apiKey')
|
||||
})
|
||||
|
||||
it('should default Grok 4.5 with high reasoning effort', () => {
|
||||
it('should default Grok 4.6 with high reasoning effort', () => {
|
||||
const models = adapter.getModels()
|
||||
|
||||
expect(models.map(model => model.id)).toEqual(['grok-4.5'])
|
||||
expect(models.map(model => model.id)).toEqual(['grok-4.6'])
|
||||
expect(models[0].defaultParameterValues).toEqual({
|
||||
reasoning_effort: 'high'
|
||||
})
|
||||
expect(models[0].parameterDefinitions.map(definition => definition.name)).toContain('reasoning_effort')
|
||||
expect(models[0].parameterDefinitions.find(definition => definition.name === 'reasoning_effort')?.allowedValues)
|
||||
.toEqual(['low', 'medium', 'high', 'xhigh'])
|
||||
})
|
||||
|
||||
it('should send reasoning_effort high through chat completions by default', async () => {
|
||||
@@ -93,7 +95,7 @@ describe('GrokAdapter', () => {
|
||||
|
||||
expect(mockOpenAIInstance.chat.completions.create).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
model: 'grok-4.5',
|
||||
model: 'grok-4.6',
|
||||
messages: [{ role: 'user', content: 'Hello' }],
|
||||
reasoning_effort: 'high'
|
||||
})
|
||||
|
||||
@@ -129,7 +129,7 @@ describe('OpenAIAdapter', () => {
|
||||
|
||||
expect(models.map(model => model.id)).toEqual([
|
||||
'gpt-5.6-terra',
|
||||
'gpt-5.6-sol',
|
||||
'gpt-6-astra',
|
||||
'gpt-5.6-luna'
|
||||
]);
|
||||
const terra = models[0];
|
||||
@@ -138,6 +138,12 @@ describe('OpenAIAdapter', () => {
|
||||
expect(terra.capabilities.supportsTools).toBe(true);
|
||||
expect(terra.capabilities.supportsReasoning).toBe(true);
|
||||
expect(terra.capabilities.maxContextLength).toBe(1050000);
|
||||
|
||||
const astra = models[1];
|
||||
expect(astra.name).toBe('GPT-6 Astra');
|
||||
expect(astra.defaultParameterValues).toMatchObject({ reasoning_effort: 'low' });
|
||||
expect(astra.parameterDefinitions.find(p => p.name === 'reasoning_effort')?.allowedValues)
|
||||
.toEqual(['low', 'medium', 'high', 'xhigh', 'max']);
|
||||
});
|
||||
|
||||
it('should have capabilities for each model', () => {
|
||||
|
||||
@@ -239,18 +239,26 @@ describe('model defaults provider env mapping', () => {
|
||||
})
|
||||
})
|
||||
|
||||
it('should include Grok 4.5 with high reasoning effort by default', () => {
|
||||
it('should include Grok 4.6 with high reasoning effort by default', () => {
|
||||
const models = getDefaultTextModels()
|
||||
|
||||
expect(models.grok).toBeDefined()
|
||||
expect(models.grok.providerMeta.id).toBe('grok')
|
||||
expect(models.grok.modelMeta.id).toBe('grok-4.5')
|
||||
expect(models.grok.modelMeta.id).toBe('grok-4.6')
|
||||
expect(models.grok.enabled).toBe(false)
|
||||
expect(models.grok.paramOverrides).toEqual({
|
||||
reasoning_effort: 'high'
|
||||
})
|
||||
})
|
||||
|
||||
it('should use GLM-5.3-Flash as the Zhipu default', () => {
|
||||
const models = getDefaultTextModels()
|
||||
|
||||
expect(models.zhipu.modelMeta.id).toBe('glm-5.3-flash')
|
||||
expect(models.zhipu.modelMeta.parameterDefinitions.find((definition) => definition.name === 'reasoning_effort')?.allowedValues)
|
||||
.toEqual(['low', 'high', 'max'])
|
||||
})
|
||||
|
||||
it('should enable Grok when VITE_XAI_API_KEY is provided', () => {
|
||||
process.env.VITE_XAI_API_KEY = 'test-xai-key'
|
||||
|
||||
|
||||
Reference in new issue
Block a user