2025-06-06 20:11:33 +00:00
|
|
|
/**
|
|
|
|
* Provider Validation Service
|
|
|
|
*
|
|
|
|
* Validates AI provider configurations before initializing the embedding system.
|
|
|
|
* This prevents startup errors when AI is enabled but providers are misconfigured.
|
|
|
|
*/
|
|
|
|
|
|
|
|
import log from "../log.js";
|
|
|
|
import options from "../options.js";
|
|
|
|
import type { EmbeddingProvider } from "./embeddings/embeddings_interface.js";
|
|
|
|
|
|
|
|
export interface ProviderValidationResult {
|
|
|
|
hasValidProviders: boolean;
|
|
|
|
validEmbeddingProviders: EmbeddingProvider[];
|
|
|
|
validChatProviders: string[];
|
|
|
|
errors: string[];
|
|
|
|
warnings: string[];
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2025-06-06 20:30:24 +00:00
|
|
|
* Simplified provider validation - just checks configuration without creating providers
|
2025-06-06 20:11:33 +00:00
|
|
|
*/
|
|
|
|
export async function validateProviders(): Promise<ProviderValidationResult> {
|
|
|
|
const result: ProviderValidationResult = {
|
|
|
|
hasValidProviders: false,
|
|
|
|
validEmbeddingProviders: [],
|
|
|
|
validChatProviders: [],
|
|
|
|
errors: [],
|
|
|
|
warnings: []
|
|
|
|
};
|
|
|
|
|
|
|
|
try {
|
|
|
|
// Check if AI is enabled
|
|
|
|
const aiEnabled = await options.getOptionBool('aiEnabled');
|
|
|
|
if (!aiEnabled) {
|
|
|
|
result.warnings.push("AI features are disabled");
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
2025-06-06 20:30:24 +00:00
|
|
|
// Check configuration only - don't create providers
|
|
|
|
await checkEmbeddingProviderConfigs(result);
|
|
|
|
await checkChatProviderConfigs(result);
|
2025-06-06 20:11:33 +00:00
|
|
|
|
2025-06-06 20:30:24 +00:00
|
|
|
// Determine if we have any valid providers based on configuration
|
|
|
|
result.hasValidProviders = result.validChatProviders.length > 0;
|
2025-06-06 20:11:33 +00:00
|
|
|
|
|
|
|
if (!result.hasValidProviders) {
|
|
|
|
result.errors.push("No valid AI providers are configured");
|
|
|
|
}
|
|
|
|
|
|
|
|
} catch (error: any) {
|
|
|
|
result.errors.push(`Error during provider validation: ${error.message || 'Unknown error'}`);
|
|
|
|
}
|
|
|
|
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2025-06-06 20:30:24 +00:00
|
|
|
* Check embedding provider configurations without creating providers
|
2025-06-06 20:11:33 +00:00
|
|
|
*/
|
2025-06-06 20:30:24 +00:00
|
|
|
async function checkEmbeddingProviderConfigs(result: ProviderValidationResult): Promise<void> {
|
2025-06-06 20:11:33 +00:00
|
|
|
try {
|
2025-06-06 20:30:24 +00:00
|
|
|
// Check OpenAI embedding configuration
|
|
|
|
const openaiApiKey = await options.getOption('openaiApiKey');
|
|
|
|
const openaiBaseUrl = await options.getOption('openaiBaseUrl');
|
|
|
|
if (openaiApiKey || openaiBaseUrl) {
|
|
|
|
if (!openaiApiKey) {
|
|
|
|
result.warnings.push("OpenAI embedding: No API key (may work with compatible endpoints)");
|
|
|
|
}
|
|
|
|
log.info("OpenAI embedding provider configuration available");
|
|
|
|
}
|
2025-06-06 20:11:33 +00:00
|
|
|
|
2025-06-06 20:30:24 +00:00
|
|
|
// Check Ollama embedding configuration
|
|
|
|
const ollamaEmbeddingBaseUrl = await options.getOption('ollamaEmbeddingBaseUrl');
|
|
|
|
if (ollamaEmbeddingBaseUrl) {
|
|
|
|
log.info("Ollama embedding provider configuration available");
|
|
|
|
}
|
|
|
|
|
|
|
|
// Check Voyage embedding configuration
|
|
|
|
const voyageApiKey = await options.getOption('voyageApiKey' as any);
|
|
|
|
if (voyageApiKey) {
|
|
|
|
log.info("Voyage embedding provider configuration available");
|
|
|
|
}
|
2025-06-06 20:11:33 +00:00
|
|
|
|
2025-06-06 20:30:24 +00:00
|
|
|
// Local provider is always available
|
|
|
|
log.info("Local embedding provider available as fallback");
|
2025-06-06 20:11:33 +00:00
|
|
|
|
|
|
|
} catch (error: any) {
|
2025-06-06 20:30:24 +00:00
|
|
|
result.errors.push(`Error checking embedding provider configs: ${error.message || 'Unknown error'}`);
|
2025-06-06 20:11:33 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2025-06-06 20:30:24 +00:00
|
|
|
* Check chat provider configurations without creating providers
|
2025-06-06 20:11:33 +00:00
|
|
|
*/
|
2025-06-06 20:30:24 +00:00
|
|
|
async function checkChatProviderConfigs(result: ProviderValidationResult): Promise<void> {
|
2025-06-06 20:11:33 +00:00
|
|
|
try {
|
|
|
|
// Check OpenAI chat provider
|
|
|
|
const openaiApiKey = await options.getOption('openaiApiKey');
|
|
|
|
const openaiBaseUrl = await options.getOption('openaiBaseUrl');
|
|
|
|
|
|
|
|
if (openaiApiKey || openaiBaseUrl) {
|
2025-06-06 20:30:24 +00:00
|
|
|
if (!openaiApiKey) {
|
|
|
|
result.warnings.push("OpenAI chat: No API key (may work with compatible endpoints)");
|
2025-06-06 20:11:33 +00:00
|
|
|
}
|
2025-06-06 20:30:24 +00:00
|
|
|
result.validChatProviders.push('openai');
|
2025-06-06 20:11:33 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Check Anthropic chat provider
|
|
|
|
const anthropicApiKey = await options.getOption('anthropicApiKey');
|
|
|
|
if (anthropicApiKey) {
|
|
|
|
result.validChatProviders.push('anthropic');
|
|
|
|
}
|
|
|
|
|
|
|
|
// Check Ollama chat provider
|
|
|
|
const ollamaBaseUrl = await options.getOption('ollamaBaseUrl');
|
|
|
|
if (ollamaBaseUrl) {
|
|
|
|
result.validChatProviders.push('ollama');
|
|
|
|
}
|
|
|
|
|
2025-06-06 20:30:24 +00:00
|
|
|
if (result.validChatProviders.length === 0) {
|
|
|
|
result.warnings.push("No chat providers configured. Please configure at least one provider.");
|
2025-06-06 20:11:33 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
} catch (error: any) {
|
2025-06-06 20:30:24 +00:00
|
|
|
result.errors.push(`Error checking chat provider configs: ${error.message || 'Unknown error'}`);
|
2025-06-06 20:11:33 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
2025-06-06 20:30:24 +00:00
|
|
|
* Check if any chat providers are configured
|
2025-06-06 20:11:33 +00:00
|
|
|
*/
|
|
|
|
export async function hasWorkingChatProviders(): Promise<boolean> {
|
|
|
|
const validation = await validateProviders();
|
|
|
|
return validation.validChatProviders.length > 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2025-06-06 20:30:24 +00:00
|
|
|
* Check if any embedding providers are configured (simplified)
|
2025-06-06 20:11:33 +00:00
|
|
|
*/
|
2025-06-06 20:30:24 +00:00
|
|
|
export async function hasWorkingEmbeddingProviders(): Promise<boolean> {
|
|
|
|
if (!(await options.getOptionBool('aiEnabled'))) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Check if any embedding provider is configured
|
|
|
|
const openaiKey = await options.getOption('openaiApiKey');
|
|
|
|
const openaiBaseUrl = await options.getOption('openaiBaseUrl');
|
|
|
|
const ollamaUrl = await options.getOption('ollamaEmbeddingBaseUrl');
|
|
|
|
const voyageKey = await options.getOption('voyageApiKey' as any);
|
|
|
|
|
|
|
|
// Local provider is always available as fallback
|
|
|
|
return !!(openaiKey || openaiBaseUrl || ollamaUrl || voyageKey) || true;
|
2025-06-06 20:11:33 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Log validation results in a user-friendly way
|
|
|
|
*/
|
|
|
|
export function logValidationResults(validation: ProviderValidationResult): void {
|
|
|
|
if (validation.hasValidProviders) {
|
|
|
|
log.info(`AI provider validation passed: ${validation.validEmbeddingProviders.length} embedding providers, ${validation.validChatProviders.length} chat providers`);
|
|
|
|
|
|
|
|
if (validation.validEmbeddingProviders.length > 0) {
|
|
|
|
log.info(`Working embedding providers: ${validation.validEmbeddingProviders.map(p => p.name).join(', ')}`);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (validation.validChatProviders.length > 0) {
|
|
|
|
log.info(`Working chat providers: ${validation.validChatProviders.join(', ')}`);
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
log.info("AI provider validation failed: No working providers found");
|
|
|
|
}
|
|
|
|
|
|
|
|
validation.warnings.forEach(warning => log.info(`Provider validation: ${warning}`));
|
|
|
|
validation.errors.forEach(error => log.error(`Provider validation: ${error}`));
|
|
|
|
}
|