From 2a1adfe3224d23874fbd6875c9d998cae54a6bd3 Mon Sep 17 00:00:00 2001 From: suyao Date: Thu, 27 Nov 2025 21:00:52 +0800 Subject: [PATCH] feat: add ppio --- packages/shared/config/providers.ts | 34 +++++++++++++++++++ src/main/apiServer/utils/index.ts | 4 ++- src/renderer/src/pages/code/CodeToolsPage.tsx | 6 ++-- .../ProviderSettings/ProviderSetting.tsx | 3 +- src/renderer/src/store/index.ts | 2 +- src/renderer/src/store/migrate.ts | 14 ++++++++ 6 files changed, 58 insertions(+), 5 deletions(-) diff --git a/packages/shared/config/providers.ts b/packages/shared/config/providers.ts index e03661bf0e..6490c61cc8 100644 --- a/packages/shared/config/providers.ts +++ b/packages/shared/config/providers.ts @@ -41,3 +41,37 @@ const SILICON_ANTHROPIC_COMPATIBLE_MODEL_SET = new Set(SILICON_ANTHROPIC_COMPATI export function isSiliconAnthropicCompatibleModel(modelId: string): boolean { return SILICON_ANTHROPIC_COMPATIBLE_MODEL_SET.has(modelId) } + +/** + * PPIO provider models that support Anthropic API endpoint. + * These models can be used with Claude Code via the Anthropic-compatible API. + * + * @see https://ppio.com/docs/model/llm-anthropic-compatibility + */ +export const PPIO_ANTHROPIC_COMPATIBLE_MODELS: readonly string[] = [ + 'moonshotai/kimi-k2-thinking', + 'minimax/minimax-m2', + 'deepseek/deepseek-v3.2-exp', + 'deepseek/deepseek-v3.1-terminus', + 'zai-org/glm-4.6', + 'moonshotai/kimi-k2-0905', + 'deepseek/deepseek-v3.1', + 'moonshotai/kimi-k2-instruct', + 'qwen/qwen3-next-80b-a3b-instruct', + 'qwen/qwen3-next-80b-a3b-thinking' +] + +/** + * Creates a Set for efficient lookup of PPIO Anthropic-compatible model IDs. + */ +const PPIO_ANTHROPIC_COMPATIBLE_MODEL_SET = new Set(PPIO_ANTHROPIC_COMPATIBLE_MODELS) + +/** + * Checks if a model ID is compatible with Anthropic API on PPIO provider. + * + * @param modelId - The model ID to check + * @returns true if the model supports Anthropic API endpoint + */ +export function isPpioAnthropicCompatibleModel(modelId: string): boolean { + return PPIO_ANTHROPIC_COMPATIBLE_MODEL_SET.has(modelId) +} diff --git a/src/main/apiServer/utils/index.ts b/src/main/apiServer/utils/index.ts index 471e734c18..fde1ff3475 100644 --- a/src/main/apiServer/utils/index.ts +++ b/src/main/apiServer/utils/index.ts @@ -1,7 +1,7 @@ import { CacheService } from '@main/services/CacheService' import { loggerService } from '@main/services/LoggerService' import { reduxService } from '@main/services/ReduxService' -import { isSiliconAnthropicCompatibleModel } from '@shared/config/providers' +import { isPpioAnthropicCompatibleModel, isSiliconAnthropicCompatibleModel } from '@shared/config/providers' import type { ApiModel, Model, Provider } from '@types' const logger = loggerService.withContext('ApiServerUtils') @@ -290,6 +290,8 @@ export const getProviderAnthropicModelChecker = (providerId: string): ((m: Model return (m: Model) => m.id.includes('claude') case 'silicon': return (m: Model) => isSiliconAnthropicCompatibleModel(m.id) + case 'ppio': + return (m: Model) => isPpioAnthropicCompatibleModel(m.id) default: // allow all models when checker not configured return () => true diff --git a/src/renderer/src/pages/code/CodeToolsPage.tsx b/src/renderer/src/pages/code/CodeToolsPage.tsx index fcb2dbf482..a4314dfef9 100644 --- a/src/renderer/src/pages/code/CodeToolsPage.tsx +++ b/src/renderer/src/pages/code/CodeToolsPage.tsx @@ -17,7 +17,7 @@ import type { EndpointType, Model } from '@renderer/types' import { getClaudeSupportedProviders } from '@renderer/utils/provider' import type { TerminalConfig } from '@shared/config/constant' import { codeTools, terminalApps } from '@shared/config/constant' -import { isSiliconAnthropicCompatibleModel } from '@shared/config/providers' +import { isPpioAnthropicCompatibleModel, isSiliconAnthropicCompatibleModel } from '@shared/config/providers' import { Alert, Avatar, Button, Checkbox, Input, Popover, Select, Space, Tooltip } from 'antd' import { ArrowUpRight, Download, FolderOpen, HelpCircle, Terminal, X } from 'lucide-react' import type { FC } from 'react' @@ -82,10 +82,12 @@ const CodeToolsPage: FC = () => { if (m.supported_endpoint_types) { return m.supported_endpoint_types.includes('anthropic') } - // Special handling for silicon provider: only specific models support Anthropic API if (m.provider === 'silicon') { return isSiliconAnthropicCompatibleModel(m.id) } + if (m.provider === 'ppio') { + return isPpioAnthropicCompatibleModel(m.id) + } return m.id.includes('claude') || CLAUDE_OFFICIAL_SUPPORTED_PROVIDERS.includes(m.provider) } diff --git a/src/renderer/src/pages/settings/ProviderSettings/ProviderSetting.tsx b/src/renderer/src/pages/settings/ProviderSettings/ProviderSetting.tsx index f341ac9229..b85690c3fb 100644 --- a/src/renderer/src/pages/settings/ProviderSettings/ProviderSetting.tsx +++ b/src/renderer/src/pages/settings/ProviderSettings/ProviderSetting.tsx @@ -85,7 +85,8 @@ const ANTHROPIC_COMPATIBLE_PROVIDER_IDS = [ SystemProviderIds.minimax, SystemProviderIds.silicon, SystemProviderIds.qiniu, - SystemProviderIds.dmxapi + SystemProviderIds.dmxapi, + SystemProviderIds.ppio ] as const type AnthropicCompatibleProviderId = (typeof ANTHROPIC_COMPATIBLE_PROVIDER_IDS)[number] diff --git a/src/renderer/src/store/index.ts b/src/renderer/src/store/index.ts index 5c562885bb..94b51474b9 100644 --- a/src/renderer/src/store/index.ts +++ b/src/renderer/src/store/index.ts @@ -67,7 +67,7 @@ const persistedReducer = persistReducer( { key: 'cherry-studio', storage, - version: 179, + version: 180, blacklist: ['runtime', 'messages', 'messageBlocks', 'tabs', 'toolPermissions'], migrate }, diff --git a/src/renderer/src/store/migrate.ts b/src/renderer/src/store/migrate.ts index 4b2e4cef89..1049da5964 100644 --- a/src/renderer/src/store/migrate.ts +++ b/src/renderer/src/store/migrate.ts @@ -2906,6 +2906,20 @@ const migrateConfig = { logger.error('migrate 179 error', error as Error) return state } + }, + '180': (state: RootState) => { + try { + state.llm.providers.forEach((provider) => { + if (provider.id === SystemProviderIds.ppio) { + provider.anthropicApiHost = 'https://api.ppinfra.com/anthropic' + } + }) + logger.info('migrate 180 success') + return state + } catch (error) { + logger.error('migrate 180 error', error as Error) + return state + } } }