import { execSync } from "node:child_process"; import type { ExtensionAPI } from "@earendil-works/pi-coding-agent"; type RemoteModel = { id?: string; name?: string; context_window?: number; max_tokens?: number; }; type ModelsResponse = { data?: RemoteModel[]; }; function getEnv(name: string): string | undefined { const value = process.env[name]?.trim(); return value ? value : undefined; } function resolveApiKey(): string | undefined { const direct = getEnv("PI_LITELLM_DISCOVERY_API_KEY"); if (direct) return direct; const command = getEnv("PI_LITELLM_DISCOVERY_API_KEY_CMD"); if (!command) return undefined; const output = execSync(command, { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"], shell: true, }).trim(); return output || undefined; } function normalizeBaseUrl(baseUrl: string): string { return baseUrl.replace(/\/+$/, ""); } function toModels(payload: ModelsResponse) { const seen = new Set(); return (payload.data ?? []) .filter((model): model is Required> & RemoteModel => Boolean(model.id)) .filter((model) => { if (seen.has(model.id)) return false; seen.add(model.id); return true; }) .map((model) => ({ id: model.id, name: model.name ?? model.id, reasoning: false, input: ["text"] as ["text"], cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }, contextWindow: model.context_window ?? 128000, maxTokens: model.max_tokens ?? 16384, })) .sort((a, b) => a.id.localeCompare(b.id)); } export default async function (pi: ExtensionAPI) { const baseUrlValue = getEnv("PI_LITELLM_DISCOVERY_BASE_URL"); if (!baseUrlValue) return; const apiKey = resolveApiKey(); if (!apiKey) { console.warn("[pi-litellm-discovery] missing API key; set PI_LITELLM_DISCOVERY_API_KEY or PI_LITELLM_DISCOVERY_API_KEY_CMD"); return; } const baseUrl = normalizeBaseUrl(baseUrlValue); const providerId = getEnv("PI_LITELLM_DISCOVERY_PROVIDER") ?? "litellm-discovery"; const providerName = getEnv("PI_LITELLM_DISCOVERY_NAME") ?? "LiteLLM Discovery"; try { const response = await fetch(`${baseUrl}/models`, { headers: { Authorization: `Bearer ${apiKey}`, }, }); if (!response.ok) { throw new Error(`model discovery failed with HTTP ${response.status}`); } const payload = (await response.json()) as ModelsResponse; const models = toModels(payload); if (models.length === 0) { console.warn(`[pi-litellm-discovery] no models returned from ${baseUrl}/models`); return; } pi.registerProvider(providerId, { name: providerName, baseUrl, apiKey, api: "openai-completions", models, }); console.log(`[pi-litellm-discovery] registered ${models.length} models for provider ${providerId}`); } catch (error) { const message = error instanceof Error ? error.message : String(error); console.warn(`[pi-litellm-discovery] ${message}`); } }