fix: add Perplexity provider support and update API host formatting (#11162)

* feat: add Perplexity provider support and update API host formatting

- Introduced `isPerplexityProvider` function to identify Perplexity providers.
- Updated `formatProviderApiHost` to handle Perplexity provider API host formatting.
- Added unit tests for Perplexity provider configuration to ensure correct API host formatting behavior.

* fix: add 'perplexity' to unsupported API version providers list
This commit is contained in:
beyondkmp 2025-11-06 10:43:33 +08:00 committed by GitHub
parent 1103449a4f
commit 83e4d4363f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 88 additions and 3 deletions

View File

@ -39,6 +39,7 @@ vi.mock('@renderer/config/providers', async (importOriginal) => {
return {
...actual,
isCherryAIProvider: vi.fn(),
isPerplexityProvider: vi.fn(),
isAnthropicProvider: vi.fn(() => false),
isAzureOpenAIProvider: vi.fn(() => false),
isGeminiProvider: vi.fn(() => false),
@ -52,7 +53,7 @@ vi.mock('@renderer/hooks/useVertexAI', () => ({
createVertexProvider: vi.fn()
}))
import { isCherryAIProvider } from '@renderer/config/providers'
import { isCherryAIProvider, isPerplexityProvider } from '@renderer/config/providers'
import { getProviderByModel } from '@renderer/services/AssistantService'
import type { Model, Provider } from '@renderer/types'
import { formatApiHost } from '@renderer/utils/api'
@ -97,6 +98,16 @@ const createCherryAIProvider = (): Provider => ({
isSystem: false
})
const createPerplexityProvider = (): Provider => ({
id: 'perplexity',
type: 'openai',
name: 'Perplexity',
apiKey: 'test-key',
apiHost: 'https://api.perplexity.ai',
models: [],
isSystem: false
})
describe('Copilot responses routing', () => {
beforeEach(() => {
;(globalThis as any).window = {
@ -195,3 +206,70 @@ describe('CherryAI provider configuration', () => {
expect(actualProvider.apiHost).toBe('')
})
})
describe('Perplexity provider configuration', () => {
beforeEach(() => {
;(globalThis as any).window = {
...(globalThis as any).window,
keyv: createWindowKeyv()
}
vi.clearAllMocks()
})
it('formats Perplexity provider apiHost with false parameter', () => {
const provider = createPerplexityProvider()
const model = createModel('sonar', 'Sonar', 'perplexity')
// Mock the functions to simulate Perplexity provider detection
vi.mocked(isCherryAIProvider).mockReturnValue(false)
vi.mocked(isPerplexityProvider).mockReturnValue(true)
vi.mocked(getProviderByModel).mockReturnValue(provider)
// Call getActualProvider which should trigger formatProviderApiHost
const actualProvider = getActualProvider(model)
// Verify that formatApiHost was called with false as the second parameter
expect(formatApiHost).toHaveBeenCalledWith('https://api.perplexity.ai', false)
expect(actualProvider.apiHost).toBe('https://api.perplexity.ai')
})
it('does not format non-Perplexity provider with false parameter', () => {
const provider = {
id: 'openai',
type: 'openai',
name: 'OpenAI',
apiKey: 'test-key',
apiHost: 'https://api.openai.com',
models: [],
isSystem: false
} as Provider
const model = createModel('gpt-4', 'GPT-4', 'openai')
// Mock the functions to simulate non-Perplexity provider
vi.mocked(isCherryAIProvider).mockReturnValue(false)
vi.mocked(isPerplexityProvider).mockReturnValue(false)
vi.mocked(getProviderByModel).mockReturnValue(provider)
// Call getActualProvider
const actualProvider = getActualProvider(model)
// Verify that formatApiHost was called with default parameters (true)
expect(formatApiHost).toHaveBeenCalledWith('https://api.openai.com')
expect(actualProvider.apiHost).toBe('https://api.openai.com/v1')
})
it('handles Perplexity provider with empty apiHost', () => {
const provider = createPerplexityProvider()
provider.apiHost = ''
const model = createModel('sonar', 'Sonar', 'perplexity')
vi.mocked(isCherryAIProvider).mockReturnValue(false)
vi.mocked(isPerplexityProvider).mockReturnValue(true)
vi.mocked(getProviderByModel).mockReturnValue(provider)
const actualProvider = getActualProvider(model)
expect(formatApiHost).toHaveBeenCalledWith('', false)
expect(actualProvider.apiHost).toBe('')
})
})

View File

@ -11,7 +11,8 @@ import {
isAzureOpenAIProvider,
isCherryAIProvider,
isGeminiProvider,
isNewApiProvider
isNewApiProvider,
isPerplexityProvider
} from '@renderer/config/providers'
import {
getAwsBedrockAccessKeyId,
@ -103,6 +104,8 @@ function formatProviderApiHost(provider: Provider): Provider {
formatted.apiHost = formatVertexApiHost(formatted)
} 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)
}

View File

@ -1490,6 +1490,10 @@ export function isCherryAIProvider(provider: Provider): boolean {
return provider.id === 'cherryai'
}
export function isPerplexityProvider(provider: Provider): boolean {
return provider.id === 'perplexity'
}
/**
* OpenAI
* @param {Provider} provider
@ -1515,7 +1519,7 @@ export function isGeminiProvider(provider: Provider): boolean {
return provider.type === 'gemini'
}
const NOT_SUPPORT_API_VERSION_PROVIDERS = ['github', 'copilot'] as const satisfies SystemProviderId[]
const NOT_SUPPORT_API_VERSION_PROVIDERS = ['github', 'copilot', 'perplexity'] as const satisfies SystemProviderId[]
export const isSupportAPIVersionProvider = (provider: Provider) => {
if (isSystemProvider(provider)) {