Code review fixes: - Add error logging to LlmProviderAdminController.testProvider catch block - Use atomic increment operations in TokenBudgetService.updateUsage to prevent race conditions - Update test expectations for atomic increment pattern Cleanup: - Remove obsolete QA automation reports All 1169 tests passing. Co-Authored-By: Claude Opus 4.5 <[email protected]>
285 lines
7.8 KiB
TypeScript
285 lines
7.8 KiB
TypeScript
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<LlmProviderResponseDto[]> {
|
|
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<LlmProviderResponseDto> {
|
|
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<LlmProviderResponseDto> {
|
|
// 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<LlmProviderResponseDto> {
|
|
// 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<void> {
|
|
// 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,
|
|
};
|
|
}
|
|
}
|