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