91 lines
2.9 KiB
TypeScript
91 lines
2.9 KiB
TypeScript
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<ChatResponse> {
|
|
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<void>((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 };
|
|
}
|
|
}
|