diff --git a/src-tauri/src/codex_config.rs b/src-tauri/src/codex_config.rs index 3b79035cd..c1da0e4c3 100644 --- a/src-tauri/src/codex_config.rs +++ b/src-tauri/src/codex_config.rs @@ -2610,7 +2610,7 @@ web_search = "disabled" for (model, host) in [ ("mimo-v2.5-pro", "https://api.xiaomimimo.com/v1"), ("mimo-v2.5", "https://token-plan-cn.xiaomimimo.com/v1"), - ("LongCat-2.0-Preview", "https://api.longcat.chat/openai/v1"), + ("LongCat-2.0", "https://api.longcat.chat/openai/v1"), ("MiniMax-M3", "https://api.minimax.io/v1"), ("MiniMax-M3", "https://api.minimaxi.com/v1"), ] { diff --git a/src-tauri/src/proxy/media_sanitizer.rs b/src-tauri/src/proxy/media_sanitizer.rs index ad30fd2fc..95b2aa305 100644 --- a/src-tauri/src/proxy/media_sanitizer.rs +++ b/src-tauri/src/proxy/media_sanitizer.rs @@ -257,6 +257,7 @@ fn known_text_only_model(model: &str) -> bool { "kat-coder-pro-v1", "kat-coder-pro-v2", "ling-2.5-1t", + "longcat-2.0", "longcat-flash-chat", "mimo-v2.5-pro", "us.deepseek.r1-v1", @@ -484,6 +485,15 @@ mod tests { ); } + #[test] + fn longcat_models_are_classified_text_only() { + // LongCat-2.0 (like the retired Flash Chat) is a text-only model; the + // preset ships it in mixed case, so the classifier must normalize first. + assert!(known_text_only_model("LongCat-2.0")); + assert!(known_text_only_model("longcat/LongCat-2.0")); + assert!(known_text_only_model("LongCat-Flash-Chat")); + } + #[test] fn explicit_text_modalities_replace_images_before_send() { let provider = provider(json!({ diff --git a/src/config/claudeDesktopProviderPresets.ts b/src/config/claudeDesktopProviderPresets.ts index 428ebc80b..9e8cb3f92 100644 --- a/src/config/claudeDesktopProviderPresets.ts +++ b/src/config/claudeDesktopProviderPresets.ts @@ -577,11 +577,7 @@ export const claudeDesktopProviderPresets: ClaudeDesktopProviderPreset[] = [ baseUrl: "https://api.longcat.chat/anthropic", mode: "proxy", apiFormat: "anthropic", - modelRoutes: brandedRoutes( - "LongCat-Flash-Chat", - "LongCat-Flash-Chat", - "LongCat-Flash-Chat", - ), + modelRoutes: brandedRoutes("LongCat-2.0", "LongCat-2.0", "LongCat-2.0"), icon: "longcat", iconColor: "#29E154", }, diff --git a/src/config/claudeProviderPresets.ts b/src/config/claudeProviderPresets.ts index 0df5f0d6a..6a8f73dc4 100644 --- a/src/config/claudeProviderPresets.ts +++ b/src/config/claudeProviderPresets.ts @@ -587,11 +587,12 @@ export const providerPresets: ProviderPreset[] = [ env: { ANTHROPIC_BASE_URL: "https://api.longcat.chat/anthropic", ANTHROPIC_AUTH_TOKEN: "", - ANTHROPIC_MODEL: "LongCat-Flash-Chat", - ANTHROPIC_DEFAULT_HAIKU_MODEL: "LongCat-Flash-Chat", - ANTHROPIC_DEFAULT_SONNET_MODEL: "LongCat-Flash-Chat", - ANTHROPIC_DEFAULT_OPUS_MODEL: "LongCat-Flash-Chat", - CLAUDE_CODE_MAX_OUTPUT_TOKENS: "6000", + ANTHROPIC_MODEL: "LongCat-2.0", + ANTHROPIC_SMALL_FAST_MODEL: "LongCat-2.0", + ANTHROPIC_DEFAULT_HAIKU_MODEL: "LongCat-2.0", + ANTHROPIC_DEFAULT_SONNET_MODEL: "LongCat-2.0", + ANTHROPIC_DEFAULT_OPUS_MODEL: "LongCat-2.0", + CLAUDE_CODE_MAX_OUTPUT_TOKENS: "131072", CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC: 1, }, }, diff --git a/src/config/codexProviderPresets.ts b/src/config/codexProviderPresets.ts index e03f8c76a..525fe6546 100644 --- a/src/config/codexProviderPresets.ts +++ b/src/config/codexProviderPresets.ts @@ -723,7 +723,7 @@ requires_openai_auth = true`, config: generateThirdPartyConfig( "longcat", "https://api.longcat.chat/openai/v1", - "LongCat-2.0-Preview", + "LongCat-2.0", ), endpointCandidates: ["https://api.longcat.chat/openai/v1"], // 美团 LongCat 官方 Codex 文档用 wire_api=responses 对自家 base_url,原生 Responses,无需路由接管转换 @@ -732,8 +732,8 @@ requires_openai_auth = true`, // 注:LongCat 的 /responses 工具类型契约文档化程度最低,建议真机冒烟一次 modelCatalog: modelCatalog([ { - model: "LongCat-2.0-Preview", - displayName: "LongCat 2.0 Preview", + model: "LongCat-2.0", + displayName: "LongCat 2.0", contextWindow: 1048576, }, ]), diff --git a/src/config/hermesProviderPresets.ts b/src/config/hermesProviderPresets.ts index a08c2d456..328eea3a6 100644 --- a/src/config/hermesProviderPresets.ts +++ b/src/config/hermesProviderPresets.ts @@ -779,13 +779,13 @@ export const hermesProviderPresets: HermesProviderPreset[] = [ base_url: "https://api.longcat.chat/openai/v1", api_key: "", api_mode: "chat_completions", - models: [{ id: "LongCat-Flash-Chat", name: "LongCat Flash Chat" }], + models: [{ id: "LongCat-2.0", name: "LongCat 2.0" }], }, category: "cn_official", icon: "longcat", iconColor: "#29E154", suggestedDefaults: { - model: { default: "LongCat-Flash-Chat", provider: "longcat" }, + model: { default: "LongCat-2.0", provider: "longcat" }, }, }, { diff --git a/src/config/openclawProviderPresets.ts b/src/config/openclawProviderPresets.ts index fabc7c6e1..cf737b542 100644 --- a/src/config/openclawProviderPresets.ts +++ b/src/config/openclawProviderPresets.ts @@ -1068,15 +1068,19 @@ export const openclawProviderPresets: OpenClawProviderPreset[] = [ websiteUrl: "https://longcat.chat/platform", apiKeyUrl: "https://longcat.chat/platform/api_keys", settingsConfig: { - baseUrl: "https://api.longcat.chat/v1", + baseUrl: "https://api.longcat.chat/openai/v1", apiKey: "", api: "openai-completions", authHeader: true, models: [ { - id: "LongCat-Flash-Chat", - name: "LongCat Flash Chat", - contextWindow: 128000, + id: "LongCat-2.0", + name: "LongCat 2.0", + reasoning: false, + input: ["text"], + contextWindow: 1048576, + maxTokens: 131072, + compat: { maxTokensField: "max_tokens" }, cost: { input: 0.001, output: 0.004 }, }, ], @@ -1087,8 +1091,8 @@ export const openclawProviderPresets: OpenClawProviderPreset[] = [ templateValues: { baseUrl: { label: "Base URL", - placeholder: "https://api.longcat.chat/v1", - defaultValue: "https://api.longcat.chat/v1", + placeholder: "https://api.longcat.chat/openai/v1", + defaultValue: "https://api.longcat.chat/openai/v1", editorValue: "", }, apiKey: { @@ -1098,8 +1102,8 @@ export const openclawProviderPresets: OpenClawProviderPreset[] = [ }, }, suggestedDefaults: { - model: { primary: "longcat/LongCat-Flash-Chat" }, - modelCatalog: { "longcat/LongCat-Flash-Chat": { alias: "LongCat" } }, + model: { primary: "longcat/LongCat-2.0" }, + modelCatalog: { "longcat/LongCat-2.0": { alias: "LongCat" } }, }, }, { diff --git a/src/config/opencodeProviderPresets.ts b/src/config/opencodeProviderPresets.ts index eee461f58..336e7380d 100644 --- a/src/config/opencodeProviderPresets.ts +++ b/src/config/opencodeProviderPresets.ts @@ -1054,12 +1054,15 @@ export const opencodeProviderPresets: OpenCodeProviderPreset[] = [ npm: "@ai-sdk/openai-compatible", name: "Longcat", options: { - baseURL: "https://api.longcat.chat/v1", + baseURL: "https://api.longcat.chat/openai/v1", apiKey: "", setCacheKey: true, }, models: { - "LongCat-Flash-Chat": { name: "LongCat Flash Chat" }, + "LongCat-2.0": { + name: "LongCat 2.0", + options: { thinking: { type: "disabled" } }, + }, }, }, category: "cn_official", @@ -1068,8 +1071,8 @@ export const opencodeProviderPresets: OpenCodeProviderPreset[] = [ templateValues: { baseURL: { label: "Base URL", - placeholder: "https://api.longcat.chat/v1", - defaultValue: "https://api.longcat.chat/v1", + placeholder: "https://api.longcat.chat/openai/v1", + defaultValue: "https://api.longcat.chat/openai/v1", editorValue: "", }, apiKey: { diff --git a/src/types.ts b/src/types.ts index 7bbeceeeb..ecc1e524b 100644 --- a/src/types.ts +++ b/src/types.ts @@ -645,6 +645,9 @@ export interface OpenClawModel { }; contextWindow?: number; maxTokens?: number; // 最大输出 token 数 + compat?: { + maxTokensField?: string; // 最大输出 token 请求字段名(如 "max_tokens") + }; } // OpenClaw 默认模型配置(agents.defaults.model) diff --git a/tests/config/codexChatProviderPresets.test.ts b/tests/config/codexChatProviderPresets.test.ts index 1dae7a2d1..a4499e551 100644 --- a/tests/config/codexChatProviderPresets.test.ts +++ b/tests/config/codexChatProviderPresets.test.ts @@ -163,7 +163,7 @@ describe("Codex Chat provider presets", () => { { contextWindows: { "doubao-seed-2-1-pro-260628": 262144 } }, ], ["Bailian", { contextWindows: { "qwen3-coder-plus": 1048576 } }], - ["Longcat", { contextWindows: { "LongCat-2.0-Preview": 1048576 } }], + ["Longcat", { contextWindows: { "LongCat-2.0": 1048576 } }], ["MiniMax", { contextWindows: { "MiniMax-M3": 1000000 } }], ["MiniMax en", { contextWindows: { "MiniMax-M3": 1000000 } }], [ diff --git a/tests/config/longcatProviderPresets.test.ts b/tests/config/longcatProviderPresets.test.ts new file mode 100644 index 000000000..1dbed39b7 --- /dev/null +++ b/tests/config/longcatProviderPresets.test.ts @@ -0,0 +1,149 @@ +import { describe, expect, it } from "vitest"; +import { claudeDesktopProviderPresets } from "@/config/claudeDesktopProviderPresets"; +import { providerPresets } from "@/config/claudeProviderPresets"; +import { codexProviderPresets } from "@/config/codexProviderPresets"; +import { hermesProviderPresets } from "@/config/hermesProviderPresets"; +import { openclawProviderPresets } from "@/config/openclawProviderPresets"; +import { opencodeProviderPresets } from "@/config/opencodeProviderPresets"; + +const LONGCAT_MODEL = "LongCat-2.0"; +const LONGCAT_DISPLAY_NAME = "LongCat 2.0"; +const LONGCAT_OPENAI_BASE_URL = "https://api.longcat.chat/openai/v1"; +const LONGCAT_BRAND = "LongCat"; +const FLASH_VARIANT = "Flash"; +const CHAT_SUFFIX = "Chat"; +const REMOVED_LONGCAT_NAMES = [ + `${LONGCAT_BRAND}-${FLASH_VARIANT}-${CHAT_SUFFIX}`, + `${LONGCAT_BRAND} ${FLASH_VARIANT} ${CHAT_SUFFIX}`, + `${LONGCAT_MODEL}-Preview`, + `${LONGCAT_DISPLAY_NAME} Preview`, +]; + +function findLongcatPreset(presets: T[]): T { + const preset = presets.find((item) => item.name === "Longcat"); + + expect(preset).toBeDefined(); + return preset!; +} + +describe("Longcat provider presets", () => { + it("uses the official LongCat 2.0 model for Claude Code", () => { + const preset = findLongcatPreset(providerPresets); + const env = (preset.settingsConfig as { env: Record }).env; + + expect(env).toMatchObject({ + ANTHROPIC_MODEL: LONGCAT_MODEL, + ANTHROPIC_SMALL_FAST_MODEL: LONGCAT_MODEL, + ANTHROPIC_DEFAULT_HAIKU_MODEL: LONGCAT_MODEL, + ANTHROPIC_DEFAULT_SONNET_MODEL: LONGCAT_MODEL, + ANTHROPIC_DEFAULT_OPUS_MODEL: LONGCAT_MODEL, + CLAUDE_CODE_MAX_OUTPUT_TOKENS: "131072", + }); + }); + + it("uses the official LongCat 2.0 model for Claude Desktop routes", () => { + const preset = findLongcatPreset(claudeDesktopProviderPresets); + + expect( + preset.modelRoutes?.map((route) => ({ + upstreamModel: route.upstreamModel, + labelOverride: route.labelOverride, + })), + ).toEqual([ + { + upstreamModel: LONGCAT_MODEL, + labelOverride: LONGCAT_MODEL, + }, + ]); + }); + + it("uses the official LongCat 2.0 model for Hermes", () => { + const preset = findLongcatPreset(hermesProviderPresets); + + expect(preset.settingsConfig.models).toEqual([ + { id: LONGCAT_MODEL, name: LONGCAT_DISPLAY_NAME }, + ]); + expect(preset.suggestedDefaults?.model).toEqual({ + default: LONGCAT_MODEL, + provider: "longcat", + }); + }); + + it("uses the official LongCat 2.0 model for OpenCode", () => { + const preset = findLongcatPreset(opencodeProviderPresets); + + expect(preset.settingsConfig.options?.baseURL).toBe( + LONGCAT_OPENAI_BASE_URL, + ); + expect(preset.templateValues?.baseURL.defaultValue).toBe( + LONGCAT_OPENAI_BASE_URL, + ); + expect(preset.templateValues?.baseURL.placeholder).toBe( + LONGCAT_OPENAI_BASE_URL, + ); + expect(preset.settingsConfig.models).toEqual({ + [LONGCAT_MODEL]: { + name: LONGCAT_DISPLAY_NAME, + options: { thinking: { type: "disabled" } }, + }, + }); + }); + + it("uses the official LongCat 2.0 model for OpenClaw", () => { + const preset = findLongcatPreset(openclawProviderPresets); + + expect(preset.settingsConfig.baseUrl).toBe(LONGCAT_OPENAI_BASE_URL); + expect(preset.templateValues?.baseUrl.defaultValue).toBe( + LONGCAT_OPENAI_BASE_URL, + ); + expect(preset.templateValues?.baseUrl.placeholder).toBe( + LONGCAT_OPENAI_BASE_URL, + ); + expect(preset.settingsConfig.models).toEqual([ + expect.objectContaining({ + id: LONGCAT_MODEL, + name: LONGCAT_DISPLAY_NAME, + reasoning: false, + input: ["text"], + contextWindow: 1048576, + maxTokens: 131072, + compat: { maxTokensField: "max_tokens" }, + }), + ]); + expect(preset.suggestedDefaults?.model).toEqual({ + primary: `longcat/${LONGCAT_MODEL}`, + }); + expect(preset.suggestedDefaults?.modelCatalog).toEqual({ + [`longcat/${LONGCAT_MODEL}`]: { alias: "LongCat" }, + }); + }); + + it("uses the official LongCat 2.0 model for Codex", () => { + const preset = findLongcatPreset(codexProviderPresets); + + expect(preset.config).toContain(`model = "${LONGCAT_MODEL}"`); + expect(preset.modelCatalog).toEqual([ + expect.objectContaining({ + model: LONGCAT_MODEL, + displayName: LONGCAT_DISPLAY_NAME, + contextWindow: 1048576, + }), + ]); + }); + + it("does not keep retired or preview Longcat model names", () => { + const longcatPresets = [ + findLongcatPreset(providerPresets), + findLongcatPreset(claudeDesktopProviderPresets), + findLongcatPreset(hermesProviderPresets), + findLongcatPreset(opencodeProviderPresets), + findLongcatPreset(openclawProviderPresets), + findLongcatPreset(codexProviderPresets), + ]; + const serializedPresets = JSON.stringify(longcatPresets); + + for (const removedName of REMOVED_LONGCAT_NAMES) { + expect(serializedPresets).not.toContain(removedName); + } + }); +});