provider-endpoint.ts
1 import { normalizeProviderEndpoint } from '@/lib/openclaw/openclaw-endpoint' 2 import { getProvider } from '@/lib/providers' 3 import { loadCredential } from '@/lib/server/credentials/credential-repository' 4 import { listCredentialIdsByProvider, resolveCredentialSecret } from '@/lib/server/credentials/credential-service' 5 import { resolveOllamaRuntimeConfig } from '@/lib/server/ollama-runtime' 6 import { loadProviderConfigs } from '@/lib/server/storage' 7 8 function clean(value: string | null | undefined): string | null { 9 if (typeof value !== 'string') return null 10 const trimmed = value.trim() 11 return trimmed || null 12 } 13 14 export function resolveProviderCredentialId(input: { 15 provider?: string | null 16 ollamaMode?: string | null 17 credentialId?: string | null 18 }): string | null { 19 const normalizedId = clean(input.credentialId) 20 21 // When no credentialId provided, auto-match by provider 22 if (!normalizedId) { 23 const provider = clean(input.provider) 24 if (!provider) return null 25 const byProvider = listCredentialIdsByProvider(provider) 26 .map((id) => [id, loadCredential(id)] as const) 27 .filter(([, cred]) => Boolean(cred)) 28 if (byProvider.length === 1) return byProvider[0][0] 29 if (byProvider.length > 1) { 30 // Pick the most recently created credential 31 return [...byProvider] 32 .sort((a, b) => ((b[1]?.createdAt as number) || 0) - ((a[1]?.createdAt as number) || 0))[0]?.[0] || null 33 } 34 return null 35 } 36 37 if (loadCredential(normalizedId)) return normalizedId 38 39 const provider = clean(input.provider) 40 if (!provider) return normalizedId 41 42 const matchingEntries = listCredentialIdsByProvider(provider) 43 .map((id) => [id, loadCredential(id)] as const) 44 .filter(([, credential]) => Boolean(credential)) 45 46 if (provider === 'ollama' && clean(input.ollamaMode) === 'cloud' && matchingEntries.length > 0) { 47 return [...matchingEntries] 48 .sort((left, right) => { 49 const leftCreatedAt = typeof left[1]?.createdAt === 'number' ? left[1].createdAt : 0 50 const rightCreatedAt = typeof right[1]?.createdAt === 'number' ? right[1].createdAt : 0 51 return rightCreatedAt - leftCreatedAt 52 })[0]?.[0] || normalizedId 53 } 54 55 return normalizedId 56 } 57 58 function resolveCredentialApiKey(credentialId?: string | null): string | null { 59 const normalized = resolveProviderCredentialId({ credentialId }) 60 if (!normalized) return null 61 return resolveCredentialSecret(normalized) 62 } 63 64 export function resolveProviderApiEndpoint(input: { 65 provider?: string | null 66 model?: string | null 67 ollamaMode?: string | null 68 credentialId?: string | null 69 apiEndpoint?: string | null 70 }): string | null { 71 const provider = clean(input.provider) 72 if (!provider) return null 73 74 const explicitEndpoint = normalizeProviderEndpoint(provider, input.apiEndpoint ?? null) 75 if (explicitEndpoint) return explicitEndpoint 76 77 if (provider === 'ollama') { 78 const credentialId = resolveProviderCredentialId(input) 79 const runtime = resolveOllamaRuntimeConfig({ 80 model: input.model, 81 ollamaMode: input.ollamaMode ?? null, 82 apiKey: resolveCredentialApiKey(credentialId), 83 apiEndpoint: null, 84 }) 85 return normalizeProviderEndpoint(provider, runtime.endpoint) || runtime.endpoint.replace(/\/+$/, '') 86 } 87 88 // Prefer provider config's custom baseUrl over the hardcoded defaultEndpoint 89 const pConfigs = loadProviderConfigs() 90 const pConfig = pConfigs[provider] 91 if (pConfig?.baseUrl) { 92 const customNormalized = normalizeProviderEndpoint(provider, pConfig.baseUrl) 93 if (customNormalized) return customNormalized 94 return pConfig.baseUrl.replace(/\/+$/, '') 95 } 96 97 const providerInfo = getProvider(provider) 98 if (!providerInfo?.defaultEndpoint) return null 99 return normalizeProviderEndpoint(provider, providerInfo.defaultEndpoint) || providerInfo.defaultEndpoint.replace(/\/+$/, '') 100 }