import { Controller, Post, Body, Logger, HttpException, HttpStatus, Inject, UseGuards, } from '@nestjs/common'; import type { AgentSessionEvent } from '@mariozechner/pi-coding-agent'; import { Throttle } from '@nestjs/throttler'; import { AgentService } from '../agent/agent.service.js'; import { AuthGuard } from '../auth/auth.guard.js'; import { CurrentUser } from '../auth/current-user.decorator.js'; import { v4 as uuid } from 'uuid'; import { ChatRequestDto } from './chat.dto.js'; interface ChatResponse { conversationId: string; text: string; } @Controller('api/chat') @UseGuards(AuthGuard) export class ChatController { private readonly logger = new Logger(ChatController.name); constructor(@Inject(AgentService) private readonly agentService: AgentService) {} @Post() @Throttle({ default: { limit: 10, ttl: 60_000 } }) async chat( @Body() body: ChatRequestDto, @CurrentUser() user: { id: string }, ): Promise { const conversationId = body.conversationId ?? uuid(); try { let agentSession = this.agentService.getSession(conversationId); if (!agentSession) { agentSession = await this.agentService.createSession(conversationId); } } catch (err) { this.logger.error( `Session creation failed for conversation=${conversationId}`, err instanceof Error ? err.stack : String(err), ); throw new HttpException('Agent session unavailable', HttpStatus.SERVICE_UNAVAILABLE); } this.logger.debug(`Handling chat request for user=${user.id}, conversation=${conversationId}`); let responseText = ''; const done = new Promise((resolve, reject) => { const timer = setTimeout(() => { cleanup(); this.logger.error(`Agent response timed out after 120s for conversation=${conversationId}`); reject(new Error('Agent response timed out')); }, 120_000); const cleanup = this.agentService.onEvent(conversationId, (event: AgentSessionEvent) => { if (event.type === 'message_update' && event.assistantMessageEvent.type === 'text_delta') { responseText += event.assistantMessageEvent.delta; } if (event.type === 'agent_end') { clearTimeout(timer); cleanup(); resolve(); } }); }); try { await this.agentService.prompt(conversationId, body.content); await done; } catch (err) { if (err instanceof HttpException) throw err; const message = err instanceof Error ? err.message : String(err); if (message.includes('timed out')) { throw new HttpException('Agent response timed out', HttpStatus.GATEWAY_TIMEOUT); } this.logger.error(`Chat prompt failed for conversation=${conversationId}`, String(err)); throw new HttpException('Agent processing failed', HttpStatus.INTERNAL_SERVER_ERROR); } return { conversationId, text: responseText }; } }