import { AI_PROVIDER, type AIProvider, registeredProviderTypes, } from "@cv/ai-provider"; import { AdminGuard, JwtAuthGuard, VerifiedScopeGuard } from "@cv/auth"; import { Inject, Optional, UseGuards } from "@nestjs/common"; import { PageInfo } from "@cv/system"; import { Args, Int, Query, Resolver } from "@nestjs/graphql"; import { AiCallLogEntryType, AiProviderStatus, QueueMessageConnection, QueueStats, SystemStatus, WorkerHealth, } from "./admin.type"; import { AiCallLogService } from "./ai-call-log.service"; import { QueueMonitorService } from "./queue-monitor.service"; @Resolver() @UseGuards(JwtAuthGuard, VerifiedScopeGuard, AdminGuard) export class AdminResolver { constructor( @Inject(AI_PROVIDER) @Optional() private readonly globalProvider: AIProvider | undefined, private readonly aiCallLogService: AiCallLogService, private readonly queueMonitorService: QueueMonitorService, ) {} @Query(() => SystemStatus) async systemStatus(): Promise { const status = new SystemStatus(); status.serverUptime = process.uptime(); status.registeredProviderTypes = registeredProviderTypes(); if (!this.globalProvider?.getStatus) { status.platformProvider = this.globalProvider ? { healthy: await this.globalProvider.isHealthy(), providerName: this.globalProvider.name, detailsJson: JSON.stringify(null), } : null; return status; } const providerStatus = await this.globalProvider.getStatus(); const platformProvider = new AiProviderStatus(); platformProvider.healthy = providerStatus.healthy; platformProvider.providerName = providerStatus.providerName; platformProvider.detailsJson = JSON.stringify( providerStatus.details ?? null, ); status.platformProvider = platformProvider; return status; } @Query(() => [AiCallLogEntryType]) aiCallLog( @Args("limit", { type: () => Int, nullable: true }) limit?: number, @Args("status", { type: () => String, nullable: true }) status?: string, @Args("providerName", { type: () => String, nullable: true }) providerName?: string, ): AiCallLogEntryType[] { const entries = this.aiCallLogService.getEntries(limit ?? undefined); return entries.filter( (e) => (!status || e.status === status) && (!providerName || e.providerName === providerName), ); } @Query(() => [AiCallLogEntryType]) async aiCallLogHistory( @Args("limit", { type: () => Int, nullable: true }) limit?: number, @Args("status", { type: () => String, nullable: true }) status?: string, @Args("providerName", { type: () => String, nullable: true }) providerName?: string, ): Promise { const rows = await this.aiCallLogService.queryHistory({ ...(limit != null ? { limit } : {}), ...(status != null ? { status } : {}), ...(providerName != null ? { providerName } : {}), }); return rows.map((r) => ({ id: r.id, timestamp: r.createdAt.toISOString(), providerName: r.providerName, durationMs: r.durationMs, promptTokens: r.promptTokens ?? undefined, completionTokens: r.completionTokens ?? undefined, model: r.model ?? undefined, finishReason: r.finishReason ?? undefined, status: r.status, error: r.error ?? undefined, userId: r.userId ?? undefined, source: r.source ?? undefined, })); } @Query(() => QueueStats) async queueStats(): Promise { const result = await this.queueMonitorService.getStats(); return QueueStats.fromDomain(result); } @Query(() => QueueMessageConnection) async queueMessages( @Args("limit", { type: () => Int, nullable: true }) limit?: number, ): Promise> { const result = await this.queueMonitorService.getMessages(limit ?? undefined); return QueueMessageConnection.fromPaginationResult({ edges: result.map((r, i) => ({ node: r, cursor: String(i) })), pageInfo: new PageInfo(false, false, null, null), totalCount: result.length, }); } @Query(() => [WorkerHealth]) async workerHealth(): Promise { const results = await this.queueMonitorService.getWorkers(); return results.map(WorkerHealth.fromDomain); } }