import { Controller, Get, Post, Patch, Delete, Body, Param, HttpCode, HttpStatus, NotFoundException, BadRequestException, Logger, } from "@nestjs/common"; import type { InputJsonValue } from "@prisma/client/runtime/library"; import { PrismaService } from "../prisma/prisma.service"; import { LlmManagerService } from "./llm-manager.service"; import { CreateLlmProviderDto, UpdateLlmProviderDto, LlmProviderResponseDto } from "./dto"; /** * Controller for LLM provider administration. * Provides CRUD operations for managing LLM provider instances. * * @example * ```typescript * // List all providers * GET /llm/admin/providers * * // Create a new provider * POST /llm/admin/providers * { * "providerType": "ollama", * "displayName": "Local Ollama", * "config": { "endpoint": "http://localhost:11434" }, * "isDefault": true * } * * // Test provider connection * POST /llm/admin/providers/:id/test * * // Reload providers from database * POST /llm/admin/reload * ``` */ @Controller("llm/admin") export class LlmProviderAdminController { private readonly logger = new Logger(LlmProviderAdminController.name); constructor( private readonly prisma: PrismaService, private readonly llmManager: LlmManagerService ) {} /** * List all LLM provider instances from the database. * Returns both enabled and disabled providers. * * @returns Array of all provider instances */ @Get("providers") async listProviders(): Promise { const providers = await this.prisma.llmProviderInstance.findMany({ orderBy: { createdAt: "asc" }, }); return providers; } /** * Get a specific LLM provider instance by ID. * * @param id - Provider instance ID * @returns Provider instance * @throws {NotFoundException} If provider not found */ @Get("providers/:id") async getProvider(@Param("id") id: string): Promise { const provider = await this.prisma.llmProviderInstance.findUnique({ where: { id }, }); if (!provider) { throw new NotFoundException(`LLM provider with ID ${id} not found`); } return provider; } /** * Create a new LLM provider instance. * If enabled, the provider will be automatically registered with the LLM manager. * * @param dto - Provider creation data * @returns Created provider instance * @throws {BadRequestException} If validation fails */ @Post("providers") @HttpCode(HttpStatus.CREATED) async createProvider(@Body() dto: CreateLlmProviderDto): Promise { // Create provider in database const provider = await this.prisma.llmProviderInstance.create({ data: { providerType: dto.providerType, displayName: dto.displayName, userId: dto.userId ?? null, config: dto.config as InputJsonValue, isDefault: dto.isDefault ?? false, isEnabled: dto.isEnabled ?? true, }, }); // Register with LLM manager if enabled if (provider.isEnabled) { await this.llmManager.registerProvider(provider); } return provider; } /** * Update an existing LLM provider instance. * The provider will be unregistered and re-registered if it's enabled. * * @param id - Provider instance ID * @param dto - Provider update data * @returns Updated provider instance * @throws {NotFoundException} If provider not found */ @Patch("providers/:id") async updateProvider( @Param("id") id: string, @Body() dto: UpdateLlmProviderDto ): Promise { // Verify provider exists const existingProvider = await this.prisma.llmProviderInstance.findUnique({ where: { id }, }); if (!existingProvider) { throw new NotFoundException(`LLM provider with ID ${id} not found`); } // Build update data with only provided fields const updateData: { displayName?: string; config?: InputJsonValue; isDefault?: boolean; isEnabled?: boolean; } = {}; if (dto.displayName !== undefined) { updateData.displayName = dto.displayName; } if (dto.config !== undefined) { updateData.config = dto.config as InputJsonValue; } if (dto.isDefault !== undefined) { updateData.isDefault = dto.isDefault; } if (dto.isEnabled !== undefined) { updateData.isEnabled = dto.isEnabled; } // Update provider in database const updatedProvider = await this.prisma.llmProviderInstance.update({ where: { id }, data: updateData, }); // Unregister old provider instance from manager await this.llmManager.unregisterProvider(id); // Re-register if still enabled if (updatedProvider.isEnabled) { await this.llmManager.registerProvider(updatedProvider); } return updatedProvider; } /** * Delete an LLM provider instance. * Cannot delete the default provider - set another provider as default first. * * @param id - Provider instance ID * @throws {NotFoundException} If provider not found * @throws {BadRequestException} If trying to delete default provider */ @Delete("providers/:id") @HttpCode(HttpStatus.NO_CONTENT) async deleteProvider(@Param("id") id: string): Promise { // Verify provider exists const provider = await this.prisma.llmProviderInstance.findUnique({ where: { id }, }); if (!provider) { throw new NotFoundException(`LLM provider with ID ${id} not found`); } // Prevent deleting default provider if (provider.isDefault) { throw new BadRequestException( "Cannot delete the default provider. Set another provider as default first." ); } // Unregister from manager await this.llmManager.unregisterProvider(id); // Delete from database await this.prisma.llmProviderInstance.delete({ where: { id }, }); } /** * Test connection to an LLM provider. * Checks if the provider is healthy and can respond to requests. * * @param id - Provider instance ID * @returns Health check result * @throws {NotFoundException} If provider not found */ @Post("providers/:id/test") async testProvider(@Param("id") id: string): Promise<{ healthy: boolean; error?: string }> { // Verify provider exists in database const provider = await this.prisma.llmProviderInstance.findUnique({ where: { id }, }); if (!provider) { throw new NotFoundException(`LLM provider with ID ${id} not found`); } // Try to get provider from manager and check health try { const providerInstance = await this.llmManager.getProviderById(id); const health = await providerInstance.checkHealth(); if (health.error !== undefined) { return { healthy: health.healthy, error: health.error, }; } return { healthy: health.healthy, }; } catch (error: unknown) { // Provider not loaded in manager (might be disabled) const errorMessage = error instanceof Error ? error.message : String(error); this.logger.warn(`Failed to test provider ${id}: ${errorMessage}`); return { healthy: false, error: "Provider not loaded in manager. Try reloading providers.", }; } } /** * Reload all enabled providers from the database. * This will clear the current provider cache and reload fresh state. * * @returns Reload result with count of loaded providers */ @Post("reload") async reloadProviders(): Promise<{ message: string; count: number }> { // Reload providers in manager await this.llmManager.reloadFromDatabase(); // Get count of enabled providers const enabledProviders = await this.prisma.llmProviderInstance.findMany({ where: { isEnabled: true }, }); return { message: "Providers reloaded successfully", count: enabledProviders.length, }; } }