diff --git a/packages/core/src/services/image-model/manager.ts b/packages/core/src/services/image-model/manager.ts index 8fe33f1f..073247de 100644 --- a/packages/core/src/services/image-model/manager.ts +++ b/packages/core/src/services/image-model/manager.ts @@ -142,9 +142,12 @@ export class ImageModelManager implements IImageModelManager { defaultConfig: ImageModelConfig ): ImageModelConfig { const legacyDefaultIds: Record = { + '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)) { diff --git a/packages/core/src/services/image/adapters/dashscope.ts b/packages/core/src/services/image/adapters/dashscope.ts index f7343b67..7bd52863 100644 --- a/packages/core/src/services/image/adapters/dashscope.ts +++ b/packages/core/src/services/image/adapters/dashscope.ts @@ -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 { diff --git a/packages/core/src/services/image/adapters/grok.ts b/packages/core/src/services/image/adapters/grok.ts index cc7eb52b..0f77fc16 100644 --- a/packages/core/src/services/image/adapters/grok.ts +++ b/packages/core/src/services/image/adapters/grok.ts @@ -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' } } diff --git a/packages/core/src/services/image/adapters/openai.ts b/packages/core/src/services/image/adapters/openai.ts index 371f14a0..d504587f 100644 --- a/packages/core/src/services/image/adapters/openai.ts +++ b/packages/core/src/services/image/adapters/openai.ts @@ -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] } ] } diff --git a/packages/core/src/services/image/adapters/openrouter.ts b/packages/core/src/services/image/adapters/openrouter.ts index a4e6da4a..150a5e44 100644 --- a/packages/core/src/services/image/adapters/openrouter.ts +++ b/packages/core/src/services/image/adapters/openrouter.ts @@ -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, diff --git a/packages/core/src/services/llm/adapters/dashscope-adapter.ts b/packages/core/src/services/llm/adapters/dashscope-adapter.ts index aed5d514..b1584536 100644 --- a/packages/core/src/services/llm/adapters/dashscope-adapter.ts +++ b/packages/core/src/services/llm/adapters/dashscope-adapter.ts @@ -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 } }, { diff --git a/packages/core/src/services/llm/adapters/gemini-adapter.ts b/packages/core/src/services/llm/adapters/gemini-adapter.ts index 5570cc81..c9090fa5 100644 --- a/packages/core/src/services/llm/adapters/gemini-adapter.ts +++ b/packages/core/src/services/llm/adapters/gemini-adapter.ts @@ -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 { diff --git a/packages/core/src/services/llm/adapters/grok-adapter.ts b/packages/core/src/services/llm/adapters/grok-adapter.ts index a39e4812..aa9c1713 100644 --- a/packages/core/src/services/llm/adapters/grok-adapter.ts +++ b/packages/core/src/services/llm/adapters/grok-adapter.ts @@ -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 } diff --git a/packages/core/src/services/llm/adapters/openai-adapter.ts b/packages/core/src/services/llm/adapters/openai-adapter.ts index 273dc272..4e327a73 100644 --- a/packages/core/src/services/llm/adapters/openai-adapter.ts +++ b/packages/core/src/services/llm/adapters/openai-adapter.ts @@ -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', diff --git a/packages/core/src/services/llm/adapters/openrouter-adapter.ts b/packages/core/src/services/llm/adapters/openrouter-adapter.ts index 78321f1c..a6e6688a 100644 --- a/packages/core/src/services/llm/adapters/openrouter-adapter.ts +++ b/packages/core/src/services/llm/adapters/openrouter-adapter.ts @@ -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 } } ] diff --git a/packages/core/src/services/llm/adapters/zhipu-adapter.ts b/packages/core/src/services/llm/adapters/zhipu-adapter.ts index 4847c0e3..93708e59 100644 --- a/packages/core/src/services/llm/adapters/zhipu-adapter.ts +++ b/packages/core/src/services/llm/adapters/zhipu-adapter.ts @@ -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 + } + }) + } } diff --git a/packages/core/src/services/model/manager.ts b/packages/core/src/services/model/manager.ts index 8847c495..aa094f75 100644 --- a/packages/core/src/services/model/manager.ts +++ b/packages/core/src/services/model/manager.ts @@ -258,11 +258,11 @@ export class ModelManager implements IModelManager { ): TextModelConfig { const legacyDefaultIds: Record = { 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)) { diff --git a/packages/core/tests/integration/prompt/multimodal-stream.real.test.ts b/packages/core/tests/integration/prompt/multimodal-stream.real.test.ts index e3ec896d..3f72e904 100644 --- a/packages/core/tests/integration/prompt/multimodal-stream.real.test.ts +++ b/packages/core/tests/integration/prompt/multimodal-stream.real.test.ts @@ -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}`, diff --git a/packages/core/tests/unit/image/dashscope-adapter.test.ts b/packages/core/tests/unit/image/dashscope-adapter.test.ts index b90a0727..840466bd 100644 --- a/packages/core/tests/unit/image/dashscope-adapter.test.ts +++ b/packages/core/tests/unit/image/dashscope-adapter.test.ts @@ -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=' }, diff --git a/packages/core/tests/unit/image/grok-adapter.test.ts b/packages/core/tests/unit/image/grok-adapter.test.ts index 4c6c5314..f95b3308 100644 --- a/packages/core/tests/unit/image/grok-adapter.test.ts +++ b/packages/core/tests/unit/image/grok-adapter.test.ts @@ -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() diff --git a/packages/core/tests/unit/image/image-default-models.test.ts b/packages/core/tests/unit/image/image-default-models.test.ts index e94cf014..c0827d46 100644 --- a/packages/core/tests/unit/image/image-default-models.test.ts +++ b/packages/core/tests/unit/image/image-default-models.test.ts @@ -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) diff --git a/packages/core/tests/unit/image/image-model-manager.test.ts b/packages/core/tests/unit/image/image-model-manager.test.ts index 5eeae7b0..66e24500 100644 --- a/packages/core/tests/unit/image/image-model-manager.test.ts +++ b/packages/core/tests/unit/image/image-model-manager.test.ts @@ -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 () => { diff --git a/packages/core/tests/unit/image/openai-adapter.test.ts b/packages/core/tests/unit/image/openai-adapter.test.ts index 03f34447..a9d3d144 100644 --- a/packages/core/tests/unit/image/openai-adapter.test.ts +++ b/packages/core/tests/unit/image/openai-adapter.test.ts @@ -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']) }) }) diff --git a/packages/core/tests/unit/image/openrouter-adapter.test.ts b/packages/core/tests/unit/image/openrouter-adapter.test.ts index ea03371e..c2c75513 100644 --- a/packages/core/tests/unit/image/openrouter-adapter.test.ts +++ b/packages/core/tests/unit/image/openrouter-adapter.test.ts @@ -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') }) diff --git a/packages/core/tests/unit/llm/gemini-adapter.test.ts b/packages/core/tests/unit/llm/gemini-adapter.test.ts index 9aafc476..7ababe18 100644 --- a/packages/core/tests/unit/llm/gemini-adapter.test.ts +++ b/packages/core/tests/unit/llm/gemini-adapter.test.ts @@ -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(); diff --git a/packages/core/tests/unit/llm/grok-adapter.test.ts b/packages/core/tests/unit/llm/grok-adapter.test.ts index 2413bf8a..3774819f 100644 --- a/packages/core/tests/unit/llm/grok-adapter.test.ts +++ b/packages/core/tests/unit/llm/grok-adapter.test.ts @@ -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' }) diff --git a/packages/core/tests/unit/llm/openai-adapter.test.ts b/packages/core/tests/unit/llm/openai-adapter.test.ts index bd079591..e3d2f0ca 100644 --- a/packages/core/tests/unit/llm/openai-adapter.test.ts +++ b/packages/core/tests/unit/llm/openai-adapter.test.ts @@ -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', () => { diff --git a/packages/core/tests/unit/model/defaults.test.ts b/packages/core/tests/unit/model/defaults.test.ts index a71a7f34..e9ebcaa1 100644 --- a/packages/core/tests/unit/model/defaults.test.ts +++ b/packages/core/tests/unit/model/defaults.test.ts @@ -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'