From 00177d26cb15374a123b350279d38d0bd0161536 Mon Sep 17 00:00:00 2001 From: suyao Date: Sat, 3 Jan 2026 05:31:19 +0800 Subject: [PATCH] refactor: again --- packages/shared/aiCore/format.ts | 142 ----------------------- packages/shared/aiCore/index.ts | 11 +- packages/shared/aiCore/providerConfig.ts | 133 ++++++++++++++++++++- 3 files changed, 134 insertions(+), 152 deletions(-) delete mode 100644 packages/shared/aiCore/format.ts diff --git a/packages/shared/aiCore/format.ts b/packages/shared/aiCore/format.ts deleted file mode 100644 index 0a44be5c06..0000000000 --- a/packages/shared/aiCore/format.ts +++ /dev/null @@ -1,142 +0,0 @@ -/** - * Provider API Host Formatting - * - * Utilities for formatting provider API hosts to work with AI SDK. - * These handle the differences between how Cherry Studio stores API hosts - * and how AI SDK expects them. - */ - -import type { MinimalProvider } from '@shared/types' -import { SystemProviderIds } from '@shared/types' -import { - isAnthropicProvider, - isAzureOpenAIProvider, - isCherryAIProvider, - isGeminiProvider, - isOllamaProvider, - isPerplexityProvider, - isVertexProvider -} from '@shared/utils/provider' - -import { - formatApiHost, - formatAzureOpenAIApiHost, - formatOllamaApiHost, - formatVertexApiHost, - isWithTrailingSharp, - routeToEndpoint, - withoutTrailingSlash -} from '../utils/url' - -/** - * Interface for environment-specific implementations - * Renderer and Main process can provide their own implementations - */ -export interface ProviderFormatContext { - vertex: { - project: string - location: string - } -} - -/** - * Default Azure OpenAI API host formatter - */ -export function defaultFormatAzureOpenAIApiHost(host: string): string { - const normalizedHost = withoutTrailingSlash(host) - ?.replace(/\/v1$/, '') - .replace(/\/openai$/, '') - // AI SDK will add /v1 - return formatApiHost(normalizedHost + '/openai', false) -} - -/** - * Format provider API host for AI SDK - * - * This function normalizes the apiHost to work with AI SDK. - * Different providers have different requirements: - * - Most providers: add /v1 suffix - * - Gemini: add /v1beta suffix - * - Some providers: no suffix needed - * - * @param provider - The provider to format - * @param context - Optional context with environment-specific implementations - * @returns Provider with formatted apiHost (and anthropicApiHost if applicable) - */ -export function formatProviderApiHost(provider: T, context: ProviderFormatContext): T { - const formatted = { ...provider } - const appendApiVersion = !isWithTrailingSharp(provider.apiHost) - // Format anthropicApiHost if present - if (formatted.anthropicApiHost) { - formatted.anthropicApiHost = formatApiHost(formatted.anthropicApiHost, appendApiVersion) - } - - // Format based on provider type - if (isAnthropicProvider(provider)) { - const baseHost = formatted.anthropicApiHost || formatted.apiHost - // AI SDK needs /v1 in baseURL - formatted.apiHost = formatApiHost(baseHost, appendApiVersion) - if (!formatted.anthropicApiHost) { - formatted.anthropicApiHost = formatted.apiHost - } - } else if (formatted.id === SystemProviderIds.copilot || formatted.id === SystemProviderIds.github) { - formatted.apiHost = formatApiHost(formatted.apiHost, false) - } else if (isOllamaProvider(formatted)) { - formatted.apiHost = formatOllamaApiHost(formatted.apiHost) - } else if (isGeminiProvider(formatted)) { - formatted.apiHost = formatApiHost(formatted.apiHost, appendApiVersion, 'v1beta') - } else if (isAzureOpenAIProvider(formatted)) { - formatted.apiHost = formatAzureOpenAIApiHost(formatted.apiHost) - } else if (isVertexProvider(formatted)) { - formatted.apiHost = formatVertexApiHost(formatted, context.vertex.project, context.vertex.location) - } else if (isCherryAIProvider(formatted)) { - formatted.apiHost = formatApiHost(formatted.apiHost, false) - } else if (isPerplexityProvider(formatted)) { - formatted.apiHost = formatApiHost(formatted.apiHost, false) - } else { - formatted.apiHost = formatApiHost(formatted.apiHost, appendApiVersion) - } - - return formatted -} - -/** - * Get the base URL for AI SDK from a formatted provider - * - * This extracts the baseURL that AI SDK expects, handling - * the '#' endpoint routing format if present. - * - * @param formattedApiHost - The formatted apiHost (after formatProviderApiHost) - * @returns The baseURL for AI SDK - */ -export function getBaseUrlForAiSdk(formattedApiHost: string): string { - const { baseURL } = routeToEndpoint(formattedApiHost) - return baseURL -} - -/** - * Get rotated API key from comma-separated keys - * - * This is the interface for API key rotation. The actual implementation - * depends on the environment (renderer uses window.keyv, main uses its own storage). - */ -export interface ApiKeyRotator { - /** - * Get the next API key in rotation - * @param providerId - The provider ID for tracking rotation - * @param keys - Comma-separated API keys - * @returns The next API key to use - */ - getRotatedKey(providerId: string, keys: string): string -} - -/** - * Simple API key rotator that always returns the first key - * Use this when rotation is not needed - */ -export const simpleKeyRotator: ApiKeyRotator = { - getRotatedKey(_providerId: string, keys: string): string { - const keyList = keys.split(',').map((k) => k.trim()) - return keyList[0] || keys - } -} diff --git a/packages/shared/aiCore/index.ts b/packages/shared/aiCore/index.ts index 5c2cda1312..097047e20a 100644 --- a/packages/shared/aiCore/index.ts +++ b/packages/shared/aiCore/index.ts @@ -4,19 +4,14 @@ * This module exports utilities for working with AI providers * that can be shared between main process and renderer process. */ - -// API host formatting -export type { ApiKeyRotator, ProviderFormatContext } from './format' +export type { AiSdkConfig, AiSdkConfigContext, ApiKeyRotator, ProviderFormatContext } from './providerConfig' export { defaultFormatAzureOpenAIApiHost, formatProviderApiHost, getBaseUrlForAiSdk, + providerToAiSdkConfig, simpleKeyRotator -} from './format' - -// AI SDK configuration -export type { AiSdkConfig, AiSdkConfigContext } from './providerConfig' -export { providerToAiSdkConfig } from './providerConfig' +} from './providerConfig' // Provider initialization export { initializeSharedProviders, SHARED_PROVIDER_CONFIGS } from './initialization' diff --git a/packages/shared/aiCore/providerConfig.ts b/packages/shared/aiCore/providerConfig.ts index 08668e352b..f57f14011f 100644 --- a/packages/shared/aiCore/providerConfig.ts +++ b/packages/shared/aiCore/providerConfig.ts @@ -8,10 +8,26 @@ import { formatPrivateKey, hasProviderConfig, ProviderConfigFactory } from '@cherrystudio/ai-core/provider' import { MinimalProvider, SystemProviderIds } from '@shared/types' import { defaultAppHeaders } from '@shared/utils' -import { isAzureOpenAIProvider, isOllamaProvider } from '@shared/utils/provider' +import { + isAnthropicProvider, + isAzureOpenAIProvider, + isCherryAIProvider, + isGeminiProvider, + isOllamaProvider, + isPerplexityProvider, + isVertexProvider +} from '@shared/utils/provider' import { isEmpty } from 'lodash' -import { routeToEndpoint } from '../utils/url' +import { + formatApiHost, + formatAzureOpenAIApiHost, + formatOllamaApiHost, + formatVertexApiHost, + isWithTrailingSharp, + routeToEndpoint, + withoutTrailingSlash +} from '../utils/url' import { getAiSdkProviderId } from './utils' /** @@ -286,3 +302,116 @@ export function providerToAiSdkConfig( } } } + +/** + * Interface for environment-specific implementations + * Renderer and Main process can provide their own implementations + */ +export interface ProviderFormatContext { + vertex: { + project: string + location: string + } +} + +/** + * Default Azure OpenAI API host formatter + */ +export function defaultFormatAzureOpenAIApiHost(host: string): string { + const normalizedHost = withoutTrailingSlash(host) + ?.replace(/\/v1$/, '') + .replace(/\/openai$/, '') + // AI SDK will add /v1 + return formatApiHost(normalizedHost + '/openai', false) +} + +/** + * Format provider API host for AI SDK + * + * This function normalizes the apiHost to work with AI SDK. + * Different providers have different requirements: + * - Most providers: add /v1 suffix + * - Gemini: add /v1beta suffix + * - Some providers: no suffix needed + * + * @param provider - The provider to format + * @param context - Optional context with environment-specific implementations + * @returns Provider with formatted apiHost (and anthropicApiHost if applicable) + */ +export function formatProviderApiHost(provider: T, context: ProviderFormatContext): T { + const formatted = { ...provider } + const appendApiVersion = !isWithTrailingSharp(provider.apiHost) + // Format anthropicApiHost if present + if (formatted.anthropicApiHost) { + formatted.anthropicApiHost = formatApiHost(formatted.anthropicApiHost, appendApiVersion) + } + + // Format based on provider type + if (isAnthropicProvider(provider)) { + const baseHost = formatted.anthropicApiHost || formatted.apiHost + // AI SDK needs /v1 in baseURL + formatted.apiHost = formatApiHost(baseHost, appendApiVersion) + if (!formatted.anthropicApiHost) { + formatted.anthropicApiHost = formatted.apiHost + } + } else if (formatted.id === SystemProviderIds.copilot || formatted.id === SystemProviderIds.github) { + formatted.apiHost = formatApiHost(formatted.apiHost, false) + } else if (isOllamaProvider(formatted)) { + formatted.apiHost = formatOllamaApiHost(formatted.apiHost) + } else if (isGeminiProvider(formatted)) { + formatted.apiHost = formatApiHost(formatted.apiHost, appendApiVersion, 'v1beta') + } else if (isAzureOpenAIProvider(formatted)) { + formatted.apiHost = formatAzureOpenAIApiHost(formatted.apiHost) + } else if (isVertexProvider(formatted)) { + formatted.apiHost = formatVertexApiHost(formatted, context.vertex.project, context.vertex.location) + } else if (isCherryAIProvider(formatted)) { + formatted.apiHost = formatApiHost(formatted.apiHost, false) + } else if (isPerplexityProvider(formatted)) { + formatted.apiHost = formatApiHost(formatted.apiHost, false) + } else { + formatted.apiHost = formatApiHost(formatted.apiHost, appendApiVersion) + } + + return formatted +} + +/** + * Get the base URL for AI SDK from a formatted provider + * + * This extracts the baseURL that AI SDK expects, handling + * the '#' endpoint routing format if present. + * + * @param formattedApiHost - The formatted apiHost (after formatProviderApiHost) + * @returns The baseURL for AI SDK + */ +export function getBaseUrlForAiSdk(formattedApiHost: string): string { + const { baseURL } = routeToEndpoint(formattedApiHost) + return baseURL +} + +/** + * Get rotated API key from comma-separated keys + * + * This is the interface for API key rotation. The actual implementation + * depends on the environment (renderer uses window.keyv, main uses its own storage). + */ +export interface ApiKeyRotator { + /** + * Get the next API key in rotation + * @param providerId - The provider ID for tracking rotation + * @param keys - Comma-separated API keys + * @returns The next API key to use + */ + getRotatedKey(providerId: string, keys: string): string +} + +/** + * Simple API key rotator that always returns the first key + * Use this when rotation is not needed + */ +export const simpleKeyRotator: ApiKeyRotator = { + getRotatedKey(_providerId: string, keys: string): string { + const keyList = keys.split(',').map((k) => k.trim()) + return keyList[0] || keys + } +}