feat: improve local provider connectivity with CORS bypass (#5458)
* feat: improve local provider connectivity with CORS bypass - Add @tauri-apps/plugin-http dependency - Implement dual fetch strategy for local vs remote providers - Auto-detect local providers (localhost, Ollama:11434, LM Studio:1234) - Make API key optional for local providers - Add comprehensive test coverage for provider fetching refactor: simplify fetchModelsFromProvider by removing preflight check logic * feat: extend config options to include custom fetch function for CORS handling * feat: conditionally use Tauri's fetch for openai-compatible providers to handle CORS
This commit is contained in:
parent
52d15802d9
commit
0890de1869
@ -32,6 +32,7 @@
|
||||
"@tauri-apps/api": "^2.5.0",
|
||||
"@tauri-apps/plugin-deep-link": "~2",
|
||||
"@tauri-apps/plugin-dialog": "^2.2.1",
|
||||
"@tauri-apps/plugin-http": "^2.2.1",
|
||||
"@tauri-apps/plugin-opener": "^2.2.7",
|
||||
"@tauri-apps/plugin-os": "^2.2.1",
|
||||
"@tauri-apps/plugin-updater": "^2.7.1",
|
||||
|
||||
@ -7,6 +7,7 @@ import {
|
||||
ModelManager,
|
||||
} from '@janhq/core'
|
||||
import { invoke } from '@tauri-apps/api/core'
|
||||
import { fetch as fetchTauri } from '@tauri-apps/plugin-http'
|
||||
import {
|
||||
ChatCompletionMessageParam,
|
||||
ChatCompletionTool,
|
||||
@ -15,7 +16,13 @@ import {
|
||||
models,
|
||||
StreamCompletionResponse,
|
||||
TokenJS,
|
||||
ConfigOptions,
|
||||
} from 'token.js'
|
||||
|
||||
// Extended config options to include custom fetch function
|
||||
type ExtendedConfigOptions = ConfigOptions & {
|
||||
fetch?: typeof fetch
|
||||
}
|
||||
import { ulid } from 'ulidx'
|
||||
import { normalizeProvider } from './models'
|
||||
import { MCPTool } from '@/types/completion'
|
||||
@ -129,7 +136,9 @@ export const sendCompletion = async (
|
||||
apiKey: provider.api_key ?? (await invoke('app_token')),
|
||||
// TODO: Retrieve from extension settings
|
||||
baseURL: provider.base_url,
|
||||
})
|
||||
// Use Tauri's fetch to avoid CORS issues only for openai-compatible provider
|
||||
...(providerName === 'openai-compatible' && { fetch: fetchTauri }),
|
||||
} as ExtendedConfigOptions)
|
||||
if (
|
||||
thread.model.id &&
|
||||
!(thread.model.id in Object.values(models).flat()) &&
|
||||
|
||||
@ -13,6 +13,8 @@ import {
|
||||
import { modelSettings } from '@/lib/predefined'
|
||||
import { fetchModels } from './models'
|
||||
import { ExtensionManager } from '@/lib/extension'
|
||||
import { fetch as fetchTauri } from '@tauri-apps/plugin-http'
|
||||
|
||||
|
||||
export const getProviders = async (): Promise<ModelProvider[]> => {
|
||||
const engines = !localStorage.getItem('migration_completed')
|
||||
@ -163,26 +165,35 @@ export const getProviders = async (): Promise<ModelProvider[]> => {
|
||||
return runtimeProviders.concat(builtinProviders as ModelProvider[])
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Fetches models from a provider's API endpoint
|
||||
* Always uses Tauri's HTTP client to bypass CORS issues
|
||||
* @param provider The provider object containing base_url and api_key
|
||||
* @returns Promise<string[]> Array of model IDs
|
||||
*/
|
||||
export const fetchModelsFromProvider = async (
|
||||
provider: ModelProvider
|
||||
): Promise<string[]> => {
|
||||
if (!provider.base_url || !provider.api_key) {
|
||||
throw new Error('Provider must have base_url and api_key configured')
|
||||
if (!provider.base_url) {
|
||||
throw new Error('Provider must have base_url configured')
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch(`${provider.base_url}/models`, {
|
||||
const headers: Record<string, string> = {
|
||||
'Content-Type': 'application/json',
|
||||
}
|
||||
|
||||
// Only add authentication headers if API key is provided
|
||||
if (provider.api_key) {
|
||||
headers['x-api-key'] = provider.api_key
|
||||
headers['Authorization'] = `Bearer ${provider.api_key}`
|
||||
}
|
||||
|
||||
// Always use Tauri's fetch to avoid CORS issues
|
||||
const response = await fetchTauri(`${provider.base_url}/models`, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'x-api-key': provider.api_key,
|
||||
'Authorization': `Bearer ${provider.api_key}`,
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
headers,
|
||||
})
|
||||
|
||||
if (!response.ok) {
|
||||
@ -213,6 +224,14 @@ export const fetchModelsFromProvider = async (
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error fetching models from provider:', error)
|
||||
|
||||
// Provide helpful error message
|
||||
if (error instanceof Error && error.message.includes('fetch')) {
|
||||
throw new Error(
|
||||
`Cannot connect to ${provider.provider} at ${provider.base_url}. Please check that the service is running and accessible.`
|
||||
)
|
||||
}
|
||||
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user