import type { LiteLLMModel, LiteLLMModelsResponse } from '../types' export const DEFAULT_LITELLM_URL = 'http://localhost:4000' const MODELS_ENDPOINT = '/v1/models' const REQUEST_TIMEOUT_MS = 3000 /** * Normalise a base URL so the rest of the plugin can rely on a * predictable shape (no trailing slash, no `/v1` suffix). */ export function normalizeBaseURL(baseURL: string = DEFAULT_LITELLM_URL): string { let normalized = baseURL.replace(/\/+$/, '') if (normalized.endsWith('/v1')) { normalized = normalized.slice(0, -3) } return normalized } /** Build a full URL for a given API endpoint. */ export function buildAPIURL(baseURL: string, endpoint: string = MODELS_ENDPOINT): string { return `${normalizeBaseURL(baseURL)}${endpoint}` } function buildHeaders(apiKey?: string, customHeaders?: Record): Record { const headers: Record = { 'Content-Type': 'application/json', } const key = apiKey ?? process.env.LITELLM_API_KEY ?? process.env.LITELLM_MASTER_KEY if (key) { headers['Authorization'] = `Bearer ${key}` } if (customHeaders) { Object.assign(headers, customHeaders) } return headers } /** Lightweight ping to see whether a LiteLLM server is reachable. */ export async function checkLiteLLMHealth( baseURL: string = DEFAULT_LITELLM_URL, apiKey?: string, customHeaders?: Record, ): Promise { try { const response = await fetch(buildAPIURL(baseURL), { method: 'GET', headers: buildHeaders(apiKey, customHeaders), signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS), }) // 401 still means a server is alive — we just don't have the right // credentials. Surface that as "unhealthy" so the user is prompted // to set LITELLM_API_KEY. return response.ok } catch { return false } } /** Discover all models exposed by a LiteLLM proxy. */ export async function discoverLiteLLMModels( baseURL: string = DEFAULT_LITELLM_URL, apiKey?: string, customHeaders?: Record, ): Promise { const url = buildAPIURL(baseURL) const response = await fetch(url, { method: 'GET', headers: buildHeaders(apiKey, customHeaders), signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS), }) if (!response.ok) { throw new Error(`LiteLLM responded with HTTP ${response.status} ${response.statusText}`) } const data = (await response.json()) as LiteLLMModelsResponse return data.data ?? [] } /** * Try the most common ports a LiteLLM proxy is started on. * The default `litellm --port` is 4000, but 8000 is also widely used * and 8080 is a common reverse-proxy default. */ export async function autoDetectLiteLLM(apiKey?: string, customHeaders?: Record): Promise { const commonPorts = [4000, 8000, 8080] for (const port of commonPorts) { const baseURL = `http://localhost:${port}` if (await checkLiteLLMHealth(baseURL, apiKey, customHeaders)) { return baseURL } } return null }