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/api": "^2.5.0",
|
||||||
"@tauri-apps/plugin-deep-link": "~2",
|
"@tauri-apps/plugin-deep-link": "~2",
|
||||||
"@tauri-apps/plugin-dialog": "^2.2.1",
|
"@tauri-apps/plugin-dialog": "^2.2.1",
|
||||||
|
"@tauri-apps/plugin-http": "^2.2.1",
|
||||||
"@tauri-apps/plugin-opener": "^2.2.7",
|
"@tauri-apps/plugin-opener": "^2.2.7",
|
||||||
"@tauri-apps/plugin-os": "^2.2.1",
|
"@tauri-apps/plugin-os": "^2.2.1",
|
||||||
"@tauri-apps/plugin-updater": "^2.7.1",
|
"@tauri-apps/plugin-updater": "^2.7.1",
|
||||||
|
|||||||
@ -7,6 +7,7 @@ import {
|
|||||||
ModelManager,
|
ModelManager,
|
||||||
} from '@janhq/core'
|
} from '@janhq/core'
|
||||||
import { invoke } from '@tauri-apps/api/core'
|
import { invoke } from '@tauri-apps/api/core'
|
||||||
|
import { fetch as fetchTauri } from '@tauri-apps/plugin-http'
|
||||||
import {
|
import {
|
||||||
ChatCompletionMessageParam,
|
ChatCompletionMessageParam,
|
||||||
ChatCompletionTool,
|
ChatCompletionTool,
|
||||||
@ -15,7 +16,13 @@ import {
|
|||||||
models,
|
models,
|
||||||
StreamCompletionResponse,
|
StreamCompletionResponse,
|
||||||
TokenJS,
|
TokenJS,
|
||||||
|
ConfigOptions,
|
||||||
} from 'token.js'
|
} from 'token.js'
|
||||||
|
|
||||||
|
// Extended config options to include custom fetch function
|
||||||
|
type ExtendedConfigOptions = ConfigOptions & {
|
||||||
|
fetch?: typeof fetch
|
||||||
|
}
|
||||||
import { ulid } from 'ulidx'
|
import { ulid } from 'ulidx'
|
||||||
import { normalizeProvider } from './models'
|
import { normalizeProvider } from './models'
|
||||||
import { MCPTool } from '@/types/completion'
|
import { MCPTool } from '@/types/completion'
|
||||||
@ -129,7 +136,9 @@ export const sendCompletion = async (
|
|||||||
apiKey: provider.api_key ?? (await invoke('app_token')),
|
apiKey: provider.api_key ?? (await invoke('app_token')),
|
||||||
// TODO: Retrieve from extension settings
|
// TODO: Retrieve from extension settings
|
||||||
baseURL: provider.base_url,
|
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 (
|
if (
|
||||||
thread.model.id &&
|
thread.model.id &&
|
||||||
!(thread.model.id in Object.values(models).flat()) &&
|
!(thread.model.id in Object.values(models).flat()) &&
|
||||||
|
|||||||
@ -13,6 +13,8 @@ import {
|
|||||||
import { modelSettings } from '@/lib/predefined'
|
import { modelSettings } from '@/lib/predefined'
|
||||||
import { fetchModels } from './models'
|
import { fetchModels } from './models'
|
||||||
import { ExtensionManager } from '@/lib/extension'
|
import { ExtensionManager } from '@/lib/extension'
|
||||||
|
import { fetch as fetchTauri } from '@tauri-apps/plugin-http'
|
||||||
|
|
||||||
|
|
||||||
export const getProviders = async (): Promise<ModelProvider[]> => {
|
export const getProviders = async (): Promise<ModelProvider[]> => {
|
||||||
const engines = !localStorage.getItem('migration_completed')
|
const engines = !localStorage.getItem('migration_completed')
|
||||||
@ -163,26 +165,35 @@ export const getProviders = async (): Promise<ModelProvider[]> => {
|
|||||||
return runtimeProviders.concat(builtinProviders as ModelProvider[])
|
return runtimeProviders.concat(builtinProviders as ModelProvider[])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fetches models from a provider's API endpoint
|
* 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
|
* @param provider The provider object containing base_url and api_key
|
||||||
* @returns Promise<string[]> Array of model IDs
|
* @returns Promise<string[]> Array of model IDs
|
||||||
*/
|
*/
|
||||||
export const fetchModelsFromProvider = async (
|
export const fetchModelsFromProvider = async (
|
||||||
provider: ModelProvider
|
provider: ModelProvider
|
||||||
): Promise<string[]> => {
|
): Promise<string[]> => {
|
||||||
if (!provider.base_url || !provider.api_key) {
|
if (!provider.base_url) {
|
||||||
throw new Error('Provider must have base_url and api_key configured')
|
throw new Error('Provider must have base_url configured')
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
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',
|
method: 'GET',
|
||||||
headers: {
|
headers,
|
||||||
'x-api-key': provider.api_key,
|
|
||||||
'Authorization': `Bearer ${provider.api_key}`,
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
},
|
|
||||||
})
|
})
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
@ -213,6 +224,14 @@ export const fetchModelsFromProvider = async (
|
|||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error fetching models from provider:', 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
|
throw error
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user