diff --git a/packages/app/src/lib/settings/ai-settings.test.ts b/packages/app/src/lib/settings/ai-settings.test.ts index 571be9cd..bfb1eb1d 100644 --- a/packages/app/src/lib/settings/ai-settings.test.ts +++ b/packages/app/src/lib/settings/ai-settings.test.ts @@ -146,18 +146,25 @@ describe("AI settings", () => { expect(settings.providers[0]?.type).toBe("openai-compatible"); }); - it("refreshes stale built-in AI provider model defaults from stored settings", async () => { + it("adds current built-in models without overwriting stored models or defaults", async () => { store.get.mockResolvedValue({ - defaultModelId: "gpt-4o", + defaultModelId: "gpt-5.5", defaultProviderId: "openai", providers: [ { apiKey: "", baseUrl: "", - defaultModelId: "gpt-4o", + defaultModelId: "gpt-5.5", enabled: false, id: "openai", - models: [{ capability: "text", enabled: true, id: "gpt-4o", name: "GPT-4o" }], + models: [ + { capability: "reasoning", enabled: false, id: "gpt-5.5", name: "My GPT-5.5" }, + { capability: "reasoning", enabled: true, id: "gpt-5.4", name: "GPT-5.4" }, + { capability: "reasoning", enabled: true, id: "gpt-5.4-mini", name: "GPT-5.4 mini" }, + { capability: "reasoning", enabled: true, id: "gpt-5.4-nano", name: "GPT-5.4 nano" }, + { capability: "image", enabled: true, id: "gpt-image-2", name: "GPT Image 2" }, + { capabilities: ["text", "tools"], enabled: true, id: "gpt-custom", name: "My custom model" } + ], name: "OpenAI", type: "openai" }, @@ -183,9 +190,32 @@ describe("AI settings", () => { expect(settings.defaultModelId).toBe("gpt-5.5"); expect(openai?.defaultModelId).toBe("gpt-5.5"); - expect(openai?.models.map((model) => model.id)).toEqual(["gpt-5.5", "gpt-5.4", "gpt-5.4-mini", "gpt-5.4-nano", "gpt-image-2"]); - expect(deepseek?.defaultModelId).toBe("deepseek-v4-pro"); - expect(deepseek?.models.map((model) => model.id)).toEqual(["deepseek-v4-pro", "deepseek-v4-flash"]); + expect(openai?.models.map((model) => model.id)).toEqual([ + "gpt-5.5", + "gpt-5.4", + "gpt-5.4-mini", + "gpt-5.4-nano", + "gpt-image-2", + "gpt-custom", + "gpt-5.6-sol", + "gpt-5.6-terra", + "gpt-5.6-luna" + ]); + expect(openai?.models.find((model) => model.id === "gpt-5.5")).toMatchObject({ + enabled: false, + name: "My GPT-5.5" + }); + expect(openai?.models.find((model) => model.id === "gpt-custom")).toMatchObject({ + capabilities: ["text", "tools"], + name: "My custom model" + }); + expect(deepseek?.defaultModelId).toBe("deepseek-chat"); + expect(deepseek?.models.map((model) => model.id)).toEqual([ + "deepseek-chat", + "deepseek-reasoner", + "deepseek-v4-pro", + "deepseek-v4-flash" + ]); }); it("preserves user-added AI provider models during normalization", async () => { @@ -199,7 +229,10 @@ describe("AI settings", () => { defaultModelId: "gpt-custom", enabled: true, id: "openai", - models: [{ capability: "text", enabled: true, id: "gpt-custom", name: "GPT Custom" }], + models: [ + { capability: "reasoning", enabled: false, id: "gpt-5.5", name: "My GPT-5.5" }, + { capability: "text", enabled: true, id: "gpt-custom", name: "GPT Custom" } + ], name: "OpenAI", type: "openai" }, @@ -219,7 +252,10 @@ describe("AI settings", () => { const settings = await getStoredAiSettings(); expect(settings.defaultModelId).toBe("gpt-custom"); - expect(settings.providers.find((provider) => provider.id === "openai")?.models.map((model) => model.id)).toEqual(["gpt-custom"]); + expect(settings.providers.find((provider) => provider.id === "openai")?.models.map((model) => model.id)).toEqual([ + "gpt-5.5", + "gpt-custom" + ]); expect(settings.providers.find((provider) => provider.id === "custom-provider-1")?.models.map((model) => model.id)).toEqual([ "writer-model" ]); diff --git a/packages/providers/src/catalog.ts b/packages/providers/src/catalog.ts index 17f8e377..888b8edc 100644 --- a/packages/providers/src/catalog.ts +++ b/packages/providers/src/catalog.ts @@ -25,10 +25,13 @@ export const defaultProviderTemplates: AiProviderConfigSeed[] = [ { apiKey: "", baseUrl: "https://api.openai.com/v1", - defaultModelId: "gpt-5.5", + defaultModelId: "gpt-5.6-sol", enabled: false, id: "openai", models: [ + { capabilities: ["text", "vision", "reasoning", "tools", "web"], enabled: true, id: "gpt-5.6-sol", name: "GPT-5.6 Sol" }, + { capabilities: ["text", "vision", "reasoning", "tools", "web"], enabled: true, id: "gpt-5.6-terra", name: "GPT-5.6 Terra" }, + { capabilities: ["text", "vision", "reasoning", "tools", "web"], enabled: true, id: "gpt-5.6-luna", name: "GPT-5.6 Luna" }, { capabilities: ["text", "vision", "reasoning", "tools", "web"], enabled: true, id: "gpt-5.5", name: "GPT-5.5" }, { capabilities: ["text", "vision", "reasoning", "tools", "web"], enabled: true, id: "gpt-5.4", name: "GPT-5.4" }, { capabilities: ["text", "vision", "reasoning", "tools", "web"], enabled: true, id: "gpt-5.4-mini", name: "GPT-5.4 mini" }, @@ -42,10 +45,13 @@ export const defaultProviderTemplates: AiProviderConfigSeed[] = [ { apiKey: "", baseUrl: "https://api.anthropic.com/v1", - defaultModelId: "claude-opus-4-7", + defaultModelId: "claude-opus-5", enabled: false, id: "anthropic", models: [ + { capabilities: ["text", "vision", "reasoning", "tools", "web"], enabled: true, id: "claude-opus-5", name: "Claude Opus 5" }, + { capabilities: ["text", "vision", "reasoning", "tools", "web"], enabled: true, id: "claude-fable-5", name: "Claude Fable 5" }, + { capabilities: ["text", "vision", "reasoning", "tools", "web"], enabled: true, id: "claude-sonnet-5", name: "Claude Sonnet 5" }, { capabilities: ["text", "vision", "reasoning", "tools", "web"], enabled: true, id: "claude-opus-4-7", name: "Claude Opus 4.7" }, { capabilities: ["text", "vision", "reasoning", "tools", "web"], enabled: true, id: "claude-sonnet-4-6", name: "Claude Sonnet 4.6" }, { capabilities: ["text", "vision", "reasoning", "tools"], enabled: true, id: "claude-haiku-4-5", name: "Claude Haiku 4.5" } @@ -57,10 +63,13 @@ export const defaultProviderTemplates: AiProviderConfigSeed[] = [ { apiKey: "", baseUrl: "https://generativelanguage.googleapis.com/v1beta", - defaultModelId: "gemini-3.1-pro-preview", + defaultModelId: "gemini-3.6-flash", enabled: false, id: "google", models: [ + { capabilities: ["text", "vision", "reasoning", "tools", "web"], enabled: true, id: "gemini-3.6-flash", name: "Gemini 3.6 Flash" }, + { capabilities: ["text", "vision", "reasoning", "tools", "web"], enabled: true, id: "gemini-3.5-flash", name: "Gemini 3.5 Flash" }, + { capabilities: ["text", "vision", "reasoning", "tools", "web"], enabled: true, id: "gemini-3.5-flash-lite", name: "Gemini 3.5 Flash-Lite" }, { capabilities: ["text", "vision", "reasoning", "tools", "web"], enabled: true, id: "gemini-3.1-pro-preview", name: "Gemini 3.1 Pro Preview" }, { capabilities: ["text", "vision", "reasoning", "tools", "web"], enabled: true, id: "gemini-3-flash-preview", name: "Gemini 3 Flash Preview" }, { @@ -91,13 +100,16 @@ export const defaultProviderTemplates: AiProviderConfigSeed[] = [ { apiKey: "", baseUrl: "https://api.mistral.ai/v1", - defaultModelId: "mistral-medium-latest", + defaultModelId: "mistral-medium-3-5", enabled: false, id: "mistral", models: [ - { capabilities: ["text", "vision", "tools"], enabled: true, id: "mistral-medium-latest", name: "Mistral Medium 3.5" }, - { capabilities: ["text", "reasoning", "tools"], enabled: true, id: "mistral-small-latest", name: "Mistral Small 4" }, - { capabilities: ["text", "vision", "tools"], enabled: true, id: "mistral-large-latest", name: "Mistral Large 3" }, + { capabilities: ["text", "vision", "tools"], enabled: true, id: "mistral-medium-3-5", name: "Mistral Medium 3.5" }, + { capabilities: ["text", "reasoning", "tools"], enabled: true, id: "mistral-small-2603", name: "Mistral Small 4" }, + { capabilities: ["text", "vision", "tools"], enabled: true, id: "mistral-large-2512", name: "Mistral Large 3" }, + { capabilities: ["text", "vision", "tools"], enabled: true, id: "mistral-medium-latest", name: "Mistral Medium 3.5 (latest alias)" }, + { capabilities: ["text", "reasoning", "tools"], enabled: true, id: "mistral-small-latest", name: "Mistral Small 4 (latest alias)" }, + { capabilities: ["text", "vision", "tools"], enabled: true, id: "mistral-large-latest", name: "Mistral Large 3 (latest alias)" }, { capabilities: ["text", "tools"], enabled: true, id: "devstral-latest", name: "Devstral 2" } ], name: "Mistral", @@ -128,6 +140,11 @@ export const defaultProviderTemplates: AiProviderConfigSeed[] = [ id: "openrouter", models: [ { capabilities: ["text", "vision", "reasoning", "tools", "web"], enabled: true, id: "openrouter/auto", name: "OpenRouter Auto" }, + { capabilities: ["text", "vision", "reasoning", "tools", "web"], enabled: true, id: "openai/gpt-5.6-sol", name: "GPT-5.6 Sol" }, + { capabilities: ["text", "vision", "reasoning", "tools", "web"], enabled: true, id: "anthropic/claude-fable-5", name: "Claude Fable 5" }, + { capabilities: ["text", "vision", "reasoning", "tools", "web"], enabled: true, id: "anthropic/claude-opus-5", name: "Claude Opus 5" }, + { capabilities: ["text", "vision", "reasoning", "tools", "web"], enabled: true, id: "anthropic/claude-sonnet-5", name: "Claude Sonnet 5" }, + { capabilities: ["text", "vision", "reasoning", "tools", "web"], enabled: true, id: "google/gemini-3.6-flash", name: "Gemini 3.6 Flash" }, { capabilities: ["text", "vision", "reasoning", "tools", "web"], enabled: true, id: "openai/gpt-5.5", name: "GPT-5.5" }, { capabilities: ["text", "vision", "reasoning", "tools"], enabled: true, id: "anthropic/claude-opus-4.7", name: "Claude Opus 4.7" }, { capabilities: ["text", "vision", "reasoning", "tools"], enabled: true, id: "anthropic/claude-sonnet-4.6", name: "Claude Sonnet 4.6" }, @@ -140,17 +157,25 @@ export const defaultProviderTemplates: AiProviderConfigSeed[] = [ { apiKey: "", baseUrl: "https://api.together.xyz/v1", - defaultModelId: "moonshotai/Kimi-K2.5", + defaultModelId: "moonshotai/Kimi-K2.6", enabled: false, id: "together", models: [ + { + capabilities: ["text", "vision", "reasoning", "tools"], + enabled: true, + id: "moonshotai/Kimi-K2.6", + name: "Kimi K2.6" + }, + { capabilities: ["text", "reasoning", "tools"], enabled: true, id: "zai-org/GLM-5.1", name: "GLM-5.1" }, + { capabilities: ["text", "reasoning", "tools"], enabled: true, id: "openai/gpt-oss-120b", name: "GPT-OSS 120B" }, + { capabilities: ["text", "reasoning", "tools"], enabled: true, id: "deepseek-ai/DeepSeek-V4-Pro", name: "DeepSeek V4 Pro" }, { capabilities: ["text", "vision", "reasoning", "tools"], enabled: true, id: "moonshotai/Kimi-K2.5", name: "Kimi K2.5" }, - { capabilities: ["text", "reasoning", "tools"], enabled: true, id: "openai/gpt-oss-120b", name: "GPT-OSS 120B" }, { capabilities: ["text", "reasoning", "tools"], enabled: true, id: "deepseek-ai/DeepSeek-R1", name: "DeepSeek R1" } ], name: "Together.ai", @@ -160,10 +185,13 @@ export const defaultProviderTemplates: AiProviderConfigSeed[] = [ { apiKey: "", baseUrl: "https://dashscope.aliyuncs.com/compatible-mode/v1", - defaultModelId: "qwen3.6-plus", + defaultModelId: "qwen3.8-max", enabled: false, id: "aliyun-bailian", models: [ + { capabilities: ["text", "vision", "reasoning", "tools", "web"], enabled: true, id: "qwen3.8-max", name: "Qwen3.8 Max" }, + { capabilities: ["text", "vision", "reasoning", "tools", "web"], enabled: true, id: "qwen3.7-plus", name: "Qwen3.7 Plus" }, + { capabilities: ["text", "vision", "reasoning", "tools", "web"], enabled: true, id: "qwen3.7-flash", name: "Qwen3.7 Flash" }, { capabilities: ["text", "vision", "reasoning", "tools", "web"], enabled: true, id: "qwen3.6-plus", name: "Qwen3.6 Plus" }, { capabilities: ["text", "reasoning", "tools", "web"], enabled: true, id: "qwen3-max", name: "Qwen3 Max" }, { capabilities: ["text", "tools"], enabled: true, id: "qwen3-coder-plus", name: "Qwen3 Coder Plus" }, @@ -191,10 +219,13 @@ export const defaultProviderTemplates: AiProviderConfigSeed[] = [ { apiKey: "", baseUrl: "https://ark.cn-beijing.volces.com/api/v3", - defaultModelId: "doubao-seed-1-6-flash-250715", + defaultModelId: "doubao-seed-evolving", enabled: false, id: "volcengine", models: [ + { capabilities: ["text", "vision", "reasoning", "tools", "web"], enabled: true, id: "doubao-seed-evolving", name: "Doubao Seed Evolving" }, + { capabilities: ["text", "vision", "reasoning", "tools", "web"], enabled: true, id: "doubao-seed-2-1-pro-260628", name: "Doubao Seed 2.1 Pro" }, + { capabilities: ["text", "vision", "reasoning", "tools", "web"], enabled: true, id: "doubao-seed-2-1-turbo-260628", name: "Doubao Seed 2.1 Turbo" }, { capabilities: ["text", "vision", "reasoning", "tools", "web"], enabled: true, id: "doubao-seed-1-6-flash-250715", name: "Doubao Seed 1.6 Flash" }, { capabilities: ["text", "vision", "reasoning", "tools", "web"], enabled: true, id: "doubao-seed-1-6-thinking-250715", name: "Doubao Seed 1.6 Thinking" }, { capabilities: ["text", "reasoning", "tools"], enabled: true, id: "deepseek-v3-2-250915", name: "DeepSeek V3.2" }, @@ -207,10 +238,11 @@ export const defaultProviderTemplates: AiProviderConfigSeed[] = [ { apiKey: "", baseUrl: "https://api.x.ai/v1", - defaultModelId: "grok-4.3", + defaultModelId: "grok-4.5", enabled: false, id: "xai", models: [ + { capabilities: ["text", "vision", "reasoning", "tools", "web"], enabled: true, id: "grok-4.5", name: "Grok 4.5" }, { capabilities: ["text", "vision", "reasoning", "tools", "web"], enabled: true, id: "grok-4.3", name: "Grok 4.3" }, { capabilities: ["text", "vision", "reasoning", "tools", "web"], enabled: true, id: "grok-4.3-fast", name: "Grok 4.3 Fast" } ], @@ -221,10 +253,13 @@ export const defaultProviderTemplates: AiProviderConfigSeed[] = [ { apiKey: "", baseUrl: "https://your-resource-name.openai.azure.com", - defaultModelId: "gpt-5.4", + defaultModelId: "gpt-5.6-sol", enabled: false, id: "azure-openai", models: [ + { capabilities: ["text", "vision", "reasoning", "tools", "web"], enabled: true, id: "gpt-5.6-sol", name: "GPT-5.6 Sol deployment" }, + { capabilities: ["text", "vision", "reasoning", "tools", "web"], enabled: true, id: "gpt-5.6-terra", name: "GPT-5.6 Terra deployment" }, + { capabilities: ["text", "vision", "reasoning", "tools", "web"], enabled: true, id: "gpt-5.6-luna", name: "GPT-5.6 Luna deployment" }, { capabilities: ["text", "vision", "reasoning", "tools", "web"], enabled: true, id: "gpt-5.4", name: "GPT-5.4 deployment" }, { capabilities: ["text", "vision", "reasoning", "tools", "web"], enabled: true, id: "gpt-5.4-mini", name: "GPT-5.4 mini deployment" }, { capabilities: ["text", "vision", "reasoning", "tools", "web"], enabled: true, id: "gpt-5.4-nano", name: "GPT-5.4 nano deployment" } @@ -236,13 +271,15 @@ export const defaultProviderTemplates: AiProviderConfigSeed[] = [ { apiKey: "", baseUrl: "http://localhost:11434/v1", - defaultModelId: "llama3.3", + defaultModelId: "qwen3.5", enabled: false, id: "ollama", models: [ + { capabilities: ["text", "vision", "reasoning", "tools"], enabled: true, id: "qwen3.5", name: "Qwen3.5 9B" }, + { capabilities: ["text", "vision", "reasoning", "tools"], enabled: true, id: "gemma4:12b", name: "Gemma 4 12B" }, + { capabilities: ["text", "reasoning", "tools"], enabled: true, id: "gpt-oss:20b", name: "GPT-OSS 20B" }, { capabilities: ["text"], enabled: true, id: "llama3.3", name: "Llama 3.3" }, - { capabilities: ["text"], enabled: true, id: "qwen3:32b", name: "Qwen3 32B" }, - { capabilities: ["text"], enabled: true, id: "gpt-oss:20b", name: "GPT-OSS 20B" } + { capabilities: ["text"], enabled: true, id: "qwen3:32b", name: "Qwen3 32B" } ], name: "Ollama", apiStyle: "openai-compatible", @@ -251,17 +288,33 @@ export const defaultProviderTemplates: AiProviderConfigSeed[] = [ ]; export const staleDefaultModelIdsByProviderId: Partial> = { - anthropic: ["claude-sonnet-4-5", "claude-opus-4-1", "claude-haiku-3-5"], - "azure-openai": ["gpt-4o"], + anthropic: ["claude-opus-4-7", "claude-sonnet-4-6", "claude-haiku-4-5", "claude-sonnet-4-5", "claude-opus-4-1", "claude-haiku-3-5"], + "aliyun-bailian": ["qwen3.6-plus", "qwen3-max", "qwen3-coder-plus", "qwen3.5-flash"], + "azure-openai": ["gpt-5.4", "gpt-5.4-mini", "gpt-5.4-nano", "gpt-4o"], deepseek: ["deepseek-chat", "deepseek-reasoner"], - google: ["gemini-2.5-pro", "gemini-2.5-flash"], + google: ["gemini-3.1-pro-preview", "gemini-3-flash-preview", "gemini-3.1-flash-lite-preview", "gemini-2.5-pro", "gemini-2.5-flash"], groq: ["llama-3.3-70b-versatile"], - mistral: ["mistral-large-latest", "mistral-small-latest"], - ollama: ["llama"], - openai: ["gpt-5.4", "gpt-5.4-mini", "gpt-5.4-nano", "gpt-image-1.5", "gpt-5", "gpt-5-mini", "gpt-4o"], - openrouter: ["openrouter/auto"], - together: ["meta-llama/Llama-3.3-70B-Instruct-Turbo"], - xai: ["grok-4"] + mistral: ["mistral-medium-latest", "mistral-small-latest", "mistral-large-latest", "devstral-latest"], + ollama: ["llama3.3", "qwen3:32b", "gpt-oss:20b", "llama"], + openai: ["gpt-5.5", "gpt-5.4", "gpt-5.4-mini", "gpt-5.4-nano", "gpt-image-2", "gpt-image-1.5", "gpt-5", "gpt-5-mini", "gpt-4o"], + openrouter: ["openrouter/auto", "openai/gpt-5.5", "anthropic/claude-opus-4.7", "anthropic/claude-sonnet-4.6", "google/gemini-3.1-pro-preview"], + together: ["moonshotai/Kimi-K2.5", "openai/gpt-oss-120b", "deepseek-ai/DeepSeek-R1", "meta-llama/Llama-3.3-70B-Instruct-Turbo"], + volcengine: ["doubao-seed-1-6-flash-250715", "doubao-seed-1-6-thinking-250715", "deepseek-v3-2-250915", "deepseek-r1-250528"], + xai: ["grok-4.3", "grok-4.3-fast", "grok-4"] +}; + +export const previousDefaultModelIdsByProviderId: Partial> = { + anthropic: ["claude-opus-4-7", "claude-sonnet-4-6", "claude-haiku-4-5"], + "aliyun-bailian": ["qwen3.6-plus", "qwen3-max", "qwen3-coder-plus", "qwen3.5-flash"], + "azure-openai": ["gpt-5.4", "gpt-5.4-mini", "gpt-5.4-nano"], + google: ["gemini-3.1-pro-preview", "gemini-3-flash-preview", "gemini-3.1-flash-lite-preview"], + mistral: ["mistral-medium-latest", "mistral-small-latest", "mistral-large-latest", "devstral-latest"], + ollama: ["llama3.3", "qwen3:32b", "gpt-oss:20b"], + openai: ["gpt-5.5", "gpt-5.4", "gpt-5.4-mini", "gpt-5.4-nano", "gpt-image-2"], + openrouter: ["openrouter/auto", "openai/gpt-5.5", "anthropic/claude-opus-4.7", "anthropic/claude-sonnet-4.6", "google/gemini-3.1-pro-preview"], + together: ["moonshotai/Kimi-K2.5", "openai/gpt-oss-120b", "deepseek-ai/DeepSeek-R1"], + volcengine: ["doubao-seed-1-6-flash-250715", "doubao-seed-1-6-thinking-250715", "deepseek-v3-2-250915", "deepseek-r1-250528"], + xai: ["grok-4.3", "grok-4.3-fast"] }; export function defaultProviderTemplateForProviderId(providerId: string): AiProviderConfigSeed | undefined { diff --git a/packages/providers/src/requests.test.ts b/packages/providers/src/requests.test.ts index 5eb276f7..78831134 100644 --- a/packages/providers/src/requests.test.ts +++ b/packages/providers/src/requests.test.ts @@ -24,6 +24,8 @@ describe("AI provider requests", () => { it("ships mainstream providers by default", () => { const settings = createDefaultAiSettings(); const providerIds = settings.providers.map((item) => item.id); + const findModelIds = (providerId: string) => + settings.providers.find((item) => item.id === providerId)?.models.map((model) => model.id); const findModelCapabilities = (providerId: string, modelId: string) => settings.providers.find((item) => item.id === providerId)?.models.find((model) => model.id === modelId)?.capabilities; @@ -45,48 +47,124 @@ describe("AI provider requests", () => { ]) ); expect(providerIds).not.toContain("openai-compatible"); - expect(settings.defaultModelId).toBe("gpt-5.5"); + expect(settings.defaultModelId).toBe("gpt-5.6-sol"); expect(settings.providers).toEqual( expect.arrayContaining([ - expect.objectContaining({ defaultModelId: "gpt-5.5", id: "openai" }), - expect.objectContaining({ defaultModelId: "claude-opus-4-7", id: "anthropic" }), - expect.objectContaining({ defaultModelId: "gemini-3.1-pro-preview", id: "google" }), + expect.objectContaining({ defaultModelId: "gpt-5.6-sol", id: "openai" }), + expect.objectContaining({ defaultModelId: "claude-opus-5", id: "anthropic" }), + expect.objectContaining({ defaultModelId: "gemini-3.6-flash", id: "google" }), expect.objectContaining({ defaultModelId: "deepseek-v4-pro", id: "deepseek" }), - expect.objectContaining({ defaultModelId: "mistral-medium-latest", id: "mistral" }), + expect.objectContaining({ defaultModelId: "mistral-medium-3-5", id: "mistral" }), expect.objectContaining({ defaultModelId: "groq/compound", id: "groq" }), expect.objectContaining({ defaultModelId: "openrouter/auto", id: "openrouter" }), - expect.objectContaining({ defaultModelId: "moonshotai/Kimi-K2.5", id: "together" }), - expect.objectContaining({ defaultModelId: "qwen3.6-plus", id: "aliyun-bailian", type: "openai-compatible" }), + expect.objectContaining({ defaultModelId: "moonshotai/Kimi-K2.6", id: "together" }), + expect.objectContaining({ defaultModelId: "qwen3.8-max", id: "aliyun-bailian", type: "openai-compatible" }), expect.objectContaining({ baseUrl: "https://api.xiaomimimo.com/v1", defaultModelId: "mimo-v2.5-pro", id: "xiaomi-mimo", type: "openai-compatible" }), - expect.objectContaining({ defaultModelId: "doubao-seed-1-6-flash-250715", id: "volcengine", type: "openai-compatible" }), - expect.objectContaining({ defaultModelId: "grok-4.3", id: "xai" }), - expect.objectContaining({ defaultModelId: "gpt-5.4", id: "azure-openai" }), - expect.objectContaining({ defaultModelId: "llama3.3", id: "ollama" }) + expect.objectContaining({ defaultModelId: "doubao-seed-evolving", id: "volcengine", type: "openai-compatible" }), + expect.objectContaining({ defaultModelId: "grok-4.5", id: "xai" }), + expect.objectContaining({ defaultModelId: "gpt-5.6-sol", id: "azure-openai" }), + expect.objectContaining({ defaultModelId: "qwen3.5", id: "ollama" }) ]) ); - expect(settings.providers.find((item) => item.id === "openai")?.models.map((model) => model.id)).toEqual([ + expect(findModelIds("openai")).toEqual([ + "gpt-5.6-sol", + "gpt-5.6-terra", + "gpt-5.6-luna", "gpt-5.5", "gpt-5.4", "gpt-5.4-mini", "gpt-5.4-nano", "gpt-image-2" ]); + expect(findModelIds("anthropic")).toEqual([ + "claude-opus-5", + "claude-fable-5", + "claude-sonnet-5", + "claude-opus-4-7", + "claude-sonnet-4-6", + "claude-haiku-4-5" + ]); + expect(findModelIds("google")).toEqual([ + "gemini-3.6-flash", + "gemini-3.5-flash", + "gemini-3.5-flash-lite", + "gemini-3.1-pro-preview", + "gemini-3-flash-preview", + "gemini-3.1-flash-lite-preview" + ]); + expect(findModelIds("mistral")).toEqual([ + "mistral-medium-3-5", + "mistral-small-2603", + "mistral-large-2512", + "mistral-medium-latest", + "mistral-small-latest", + "mistral-large-latest", + "devstral-latest" + ]); + expect(findModelIds("openrouter")).toEqual([ + "openrouter/auto", + "openai/gpt-5.6-sol", + "anthropic/claude-fable-5", + "anthropic/claude-opus-5", + "anthropic/claude-sonnet-5", + "google/gemini-3.6-flash", + "openai/gpt-5.5", + "anthropic/claude-opus-4.7", + "anthropic/claude-sonnet-4.6", + "google/gemini-3.1-pro-preview" + ]); + expect(findModelIds("together")).toEqual([ + "moonshotai/Kimi-K2.6", + "zai-org/GLM-5.1", + "openai/gpt-oss-120b", + "deepseek-ai/DeepSeek-V4-Pro", + "moonshotai/Kimi-K2.5", + "deepseek-ai/DeepSeek-R1" + ]); + expect(findModelIds("aliyun-bailian")).toEqual([ + "qwen3.8-max", + "qwen3.7-plus", + "qwen3.7-flash", + "qwen3.6-plus", + "qwen3-max", + "qwen3-coder-plus", + "qwen3.5-flash" + ]); + expect(findModelIds("volcengine")).toEqual([ + "doubao-seed-evolving", + "doubao-seed-2-1-pro-260628", + "doubao-seed-2-1-turbo-260628", + "doubao-seed-1-6-flash-250715", + "doubao-seed-1-6-thinking-250715", + "deepseek-v3-2-250915", + "deepseek-r1-250528" + ]); + expect(findModelIds("xai")).toEqual(["grok-4.5", "grok-4.3", "grok-4.3-fast"]); + expect(findModelIds("azure-openai")).toEqual([ + "gpt-5.6-sol", + "gpt-5.6-terra", + "gpt-5.6-luna", + "gpt-5.4", + "gpt-5.4-mini", + "gpt-5.4-nano" + ]); + expect(findModelIds("ollama")).toEqual(["qwen3.5", "gemma4:12b", "gpt-oss:20b", "llama3.3", "qwen3:32b"]); expect(settings.providers.find((item) => item.id === "deepseek")?.models.map((model) => model.id)).not.toContain( "deepseek-chat" ); - expect(findModelCapabilities("openai", "gpt-5.5")).toEqual(["text", "vision", "reasoning", "tools", "web"]); + expect(findModelCapabilities("openai", "gpt-5.6-sol")).toEqual(["text", "vision", "reasoning", "tools", "web"]); expect(findModelCapabilities("anthropic", "claude-haiku-4-5")).toEqual([ "text", "vision", "reasoning", "tools" ]); - expect(findModelCapabilities("google", "gemini-3-flash-preview")).toEqual([ + expect(findModelCapabilities("google", "gemini-3.6-flash")).toEqual([ "text", "vision", "reasoning", @@ -94,14 +172,14 @@ describe("AI provider requests", () => { "web" ]); expect(findModelCapabilities("deepseek", "deepseek-v4-pro")).toEqual(["text", "reasoning", "tools"]); - expect(findModelCapabilities("mistral", "mistral-large-latest")).toEqual(["text", "vision", "tools"]); - expect(findModelCapabilities("together", "moonshotai/Kimi-K2.5")).toEqual([ + expect(findModelCapabilities("mistral", "mistral-large-2512")).toEqual(["text", "vision", "tools"]); + expect(findModelCapabilities("together", "moonshotai/Kimi-K2.6")).toEqual([ "text", "vision", "reasoning", "tools" ]); - expect(findModelCapabilities("aliyun-bailian", "qwen3.6-plus")).toEqual([ + expect(findModelCapabilities("aliyun-bailian", "qwen3.8-max")).toEqual([ "text", "vision", "reasoning", @@ -117,30 +195,18 @@ describe("AI provider requests", () => { ]); expect(findModelCapabilities("xiaomi-mimo", "mimo-v2.5-pro")).toContain("web"); expect(findModelCapabilities("xiaomi-mimo", "mimo-v2.5-flash")).toContain("web"); - expect(findModelCapabilities("volcengine", "doubao-seed-1-6-flash-250715")).toEqual([ + expect(findModelCapabilities("volcengine", "doubao-seed-evolving")).toEqual([ "text", "vision", "reasoning", "tools", "web" ]); - expect(settings.providers.find((item) => item.id === "aliyun-bailian")?.models.map((model) => model.id)).toEqual([ - "qwen3.6-plus", - "qwen3-max", - "qwen3-coder-plus", - "qwen3.5-flash" - ]); expect(settings.providers.find((item) => item.id === "xiaomi-mimo")?.models.map((model) => model.id)).toEqual([ "mimo-v2.5-pro", "mimo-v2.5", "mimo-v2.5-flash" ]); - expect(settings.providers.find((item) => item.id === "volcengine")?.models.map((model) => model.id)).toEqual([ - "doubao-seed-1-6-flash-250715", - "doubao-seed-1-6-thinking-250715", - "deepseek-v3-2-250915", - "deepseek-r1-250528" - ]); }); it("builds provider-specific model list requests", () => { diff --git a/packages/providers/src/settings.ts b/packages/providers/src/settings.ts index 7cf2c770..6016634d 100644 --- a/packages/providers/src/settings.ts +++ b/packages/providers/src/settings.ts @@ -4,6 +4,7 @@ import { defaultApiUrlForStoredProvider, defaultProviderTemplateForProviderId, defaultProviderTemplates, + previousDefaultModelIdsByProviderId, staleDefaultModelIdsByProviderId } from "./catalog"; import { enrichAiProviderModelCapabilities, readModelCapabilities } from "./capabilities"; @@ -22,11 +23,11 @@ const legacyBuiltInProviderIds = new Set(["openai-compatible"]); export function createDefaultAiSettings(): AiProviderSettings { return { - agentDefaultModelId: "gpt-5.5", + agentDefaultModelId: "gpt-5.6-sol", agentDefaultProviderId: "openai", - defaultModelId: "gpt-5.5", + defaultModelId: "gpt-5.6-sol", defaultProviderId: "openai", - inlineDefaultModelId: "gpt-5.5", + inlineDefaultModelId: "gpt-5.6-sol", inlineDefaultProviderId: "openai", providers: defaultProviderTemplates.map(cloneProvider) }; @@ -110,7 +111,7 @@ function normalizeProvider(value: unknown): AiProviderConfig | null { const shouldRefreshDefaultModels = shouldRefreshStoredDefaultModels(providerId, storedModels); const models = shouldRefreshDefaultModels && defaultProvider - ? defaultProvider.models.map(cloneModel) + ? mergeDefaultModels(storedModels, defaultProvider.models) : storedModels.length > 0 ? storedModels : defaultProvider?.models.map(cloneModel) ?? [ @@ -146,9 +147,19 @@ function shouldRefreshStoredDefaultModels(providerId: string, models: AiProvider if (!staleModelIds || models.length === 0 || providerId.startsWith("custom-provider-")) return false; const staleModelIdSet = new Set(staleModelIds); + const storedModelIdSet = new Set(models.map((model) => model.id)); + const previousDefaultModelIds = previousDefaultModelIdsByProviderId[providerId]; + const containsPreviousDefaults = previousDefaultModelIds?.every((modelId) => storedModelIdSet.has(modelId)) ?? false; - // Only auto-upgrade lists that still look exactly like Markra's older built-in seeds. - return models.every((model) => staleModelIdSet.has(model.id)); + // A complete previous seed may also contain user-added models; merging keeps those entries intact. + return containsPreviousDefaults || models.every((model) => staleModelIdSet.has(model.id)); +} + +function mergeDefaultModels(storedModels: AiProviderModel[], defaultModels: AiProviderModelSeed[]) { + const storedModelIds = new Set(storedModels.map((model) => model.id)); + + // Keep stored entries and their order; only append built-in models that are missing. + return [...storedModels, ...defaultModels.filter((model) => !storedModelIds.has(model.id)).map(cloneModel)]; } function normalizeModel(value: unknown): AiProviderModel | null {