mirror of
https://github.com/CherryHQ/cherry-studio.git
synced 2026-01-12 00:49:14 +08:00
refactor: again
This commit is contained in:
parent
78f60deb91
commit
00177d26cb
@ -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<T extends MinimalProvider>(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
|
||||
}
|
||||
}
|
||||
@ -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'
|
||||
|
||||
@ -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<T extends MinimalProvider>(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
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user