import { Repository } from 'typeorm'; import { ToolCall, ToolCallResult, ResultType } from '../entities'; import { StartCallData, CallHistoryFilters, PaginatedResult } from '../dto'; export class ToolLoggerService { constructor( private readonly toolCallRepo: Repository, private readonly resultRepo: Repository ) {} async startCall(data: StartCallData): Promise { const call = this.toolCallRepo.create({ tenantId: data.tenantId, toolName: data.toolName, parameters: data.parameters, agentId: data.agentId, conversationId: data.conversationId, callerType: data.callerType, calledByUserId: data.userId, status: 'running', startedAt: new Date(), }); const saved = await this.toolCallRepo.save(call); return saved.id; } async completeCall(callId: string, result: any): Promise { const call = await this.toolCallRepo.findOne({ where: { id: callId } }); if (!call) return; const duration = Date.now() - call.startedAt.getTime(); await this.toolCallRepo.update(callId, { status: 'success', completedAt: new Date(), durationMs: duration, }); await this.resultRepo.save({ toolCallId: callId, result, resultType: this.getResultType(result), }); } async failCall(callId: string, errorMessage: string, errorCode: string): Promise { const call = await this.toolCallRepo.findOne({ where: { id: callId } }); if (!call) return; const duration = Date.now() - call.startedAt.getTime(); await this.toolCallRepo.update(callId, { status: 'error', completedAt: new Date(), durationMs: duration, }); await this.resultRepo.save({ toolCallId: callId, resultType: 'error', errorMessage, errorCode, }); } async timeoutCall(callId: string): Promise { const call = await this.toolCallRepo.findOne({ where: { id: callId } }); if (!call) return; const duration = Date.now() - call.startedAt.getTime(); await this.toolCallRepo.update(callId, { status: 'timeout', completedAt: new Date(), durationMs: duration, }); await this.resultRepo.save({ toolCallId: callId, resultType: 'error', errorMessage: 'Tool execution timed out', errorCode: 'TIMEOUT', }); } async getCallHistory( tenantId: string, filters: CallHistoryFilters ): Promise> { const qb = this.toolCallRepo .createQueryBuilder('tc') .leftJoinAndSelect('tc.result', 'result') .where('tc.tenant_id = :tenantId', { tenantId }); if (filters.toolName) { qb.andWhere('tc.tool_name = :toolName', { toolName: filters.toolName }); } if (filters.status) { qb.andWhere('tc.status = :status', { status: filters.status }); } if (filters.startDate) { qb.andWhere('tc.created_at >= :startDate', { startDate: filters.startDate }); } if (filters.endDate) { qb.andWhere('tc.created_at <= :endDate', { endDate: filters.endDate }); } qb.orderBy('tc.created_at', 'DESC'); qb.skip((filters.page - 1) * filters.limit); qb.take(filters.limit); const [data, total] = await qb.getManyAndCount(); return { data, total, page: filters.page, limit: filters.limit }; } async getCallById(id: string, tenantId: string): Promise { return this.toolCallRepo.findOne({ where: { id, tenantId }, relations: ['result'], }); } async getToolStats( tenantId: string, startDate: Date, endDate: Date ): Promise<{ toolName: string; totalCalls: number; successfulCalls: number; failedCalls: number; avgDurationMs: number; }[]> { const result = await this.toolCallRepo .createQueryBuilder('tc') .select('tc.tool_name', 'toolName') .addSelect('COUNT(*)', 'totalCalls') .addSelect("COUNT(*) FILTER (WHERE tc.status = 'success')", 'successfulCalls') .addSelect("COUNT(*) FILTER (WHERE tc.status = 'error')", 'failedCalls') .addSelect('AVG(tc.duration_ms)', 'avgDurationMs') .where('tc.tenant_id = :tenantId', { tenantId }) .andWhere('tc.created_at BETWEEN :startDate AND :endDate', { startDate, endDate }) .groupBy('tc.tool_name') .orderBy('totalCalls', 'DESC') .getRawMany(); return result.map((r) => ({ toolName: r.toolName, totalCalls: parseInt(r.totalCalls) || 0, successfulCalls: parseInt(r.successfulCalls) || 0, failedCalls: parseInt(r.failedCalls) || 0, avgDurationMs: parseFloat(r.avgDurationMs) || 0, })); } private getResultType(result: any): ResultType { if (result === null) return 'null'; if (Array.isArray(result)) return 'array'; const type = typeof result; if (type === 'object') return 'object'; if (type === 'string') return 'string'; if (type === 'number') return 'number'; if (type === 'boolean') return 'boolean'; return 'object'; } }