NestJS Integration
Seamlessly integrate AgentSea with NestJS using decorators, modules, and dependency injection for building enterprise-grade agentic applications.
Installation
Install both core and NestJS packages:
bash
# Install packages
pnpm add @lov3kaizen/agentsea-core @lov3kaizen/agentsea-nestjs
# Install NestJS dependencies
pnpm add @nestjs/common @nestjs/core reflect-metadata rxjsModule Setup
Configure the AgentSea module in your NestJS application:
typescript
// app.module.ts
import { Module } from '@nestjs/common';
import { AgenticModule } from '@lov3kaizen/agentsea-nestjs';
import { AnthropicProvider } from '@lov3kaizen/agentsea-core';
@Module({
imports: [
AgenticModule.forRoot({
provider: new AnthropicProvider(process.env.ANTHROPIC_API_KEY),
defaultConfig: {
model: 'claude-sonnet-4-20250514',
provider: 'anthropic',
temperature: 0.7,
maxTokens: 2048,
},
}),
],
})
export class AppModule {}Async Configuration
Use async configuration for dynamic setup:
typescript
// app.module.ts
import { Module } from '@nestjs/common';
import { ConfigModule, ConfigService } from '@nestjs/config';
import { AgenticModule } from '@lov3kaizen/agentsea-nestjs';
import { AnthropicProvider } from '@lov3kaizen/agentsea-core';
@Module({
imports: [
ConfigModule.forRoot(),
AgenticModule.forRootAsync({
imports: [ConfigModule],
inject: [ConfigService],
useFactory: async (config: ConfigService) => ({
provider: new AnthropicProvider(config.get('ANTHROPIC_API_KEY')),
defaultConfig: {
model: config.get('AGENT_MODEL', 'claude-sonnet-4-20250514'),
provider: 'anthropic',
temperature: Number(config.get('AGENT_TEMPERATURE', 0.7)),
},
}),
}),
],
})
export class AppModule {}Agent Service
Create an agent service using dependency injection:
typescript
// chat.service.ts
import { Injectable } from '@nestjs/common';
import { Agent, ToolRegistry, BufferMemory } from '@lov3kaizen/agentsea-core';
import { InjectAgent } from '@lov3kaizen/agentsea-nestjs';
@Injectable()
export class ChatService {
constructor(
@InjectAgent() private readonly agent: Agent,
private readonly toolRegistry: ToolRegistry,
private readonly memory: BufferMemory,
) {}
async chat(userId: string, message: string) {
const response = await this.agent.execute(message, {
conversationId: userId,
sessionData: { userId },
history: await this.memory.load(userId),
});
await this.memory.save(userId, [
...await this.memory.load(userId),
{ role: 'user', content: message },
{ role: 'assistant', content: response.content },
]);
return response;
}
}Agent Controller
Create REST endpoints for your agents:
typescript
// chat.controller.ts
import { Controller, Post, Body, Param } from '@nestjs/common';
import { ChatService } from './chat.service';
@Controller('chat')
export class ChatController {
constructor(private readonly chatService: ChatService) {}
@Post(':userId')
async chat(
@Param('userId') userId: string,
@Body('message') message: string,
) {
const response = await this.chatService.chat(userId, message);
return {
content: response.content,
toolCalls: response.toolCalls,
metadata: response.metadata,
};
}
@Post(':userId/stream')
async stream(
@Param('userId') userId: string,
@Body('message') message: string,
) {
// Implement streaming response
// Use SSE or WebSocket for streaming
}
}Custom Decorators
AgentSea provides decorators for common patterns:
@Agent Decorator
typescript
import { Injectable } from '@nestjs/common';
import { AgentDecorator } from '@lov3kaizen/agentsea-nestjs';
@Injectable()
export class SupportService {
@AgentDecorator({
name: 'support-agent',
systemPrompt: 'You are a helpful customer support agent.',
})
async handleSupport(message: string, userId: string) {
// Method will be automatically wrapped with agent execution
return message;
}
}@Tool Decorator
typescript
import { Injectable } from '@nestjs/common';
import { ToolDecorator } from '@lov3kaizen/agentsea-nestjs';
import { z } from 'zod';
@Injectable()
export class DatabaseService {
@ToolDecorator({
name: 'query_users',
description: 'Query users from the database',
inputSchema: z.object({
limit: z.number().default(10),
}),
})
async queryUsers(input: { limit: number }) {
const users = await this.db.users.findMany({
take: input.limit,
});
return {
success: true,
data: users,
count: users.length,
};
}
}Guards and Interceptors
Rate Limiting
typescript
import { Controller, Post, UseGuards } from '@nestjs/common';
import { RateLimitGuard, RateLimit } from '@lov3kaizen/agentsea-nestjs';
@Controller('chat')
@UseGuards(RateLimitGuard)
export class ChatController {
@Post()
@RateLimit({ maxRequests: 10, windowMs: 60000 }) // 10 requests per minute
async chat(@Body('message') message: string) {
return this.chatService.chat(message);
}
}Logging Interceptor
typescript
// logging.interceptor.ts
import {
Injectable,
NestInterceptor,
ExecutionContext,
CallHandler,
} from '@nestjs/common';
import { Observable } from 'rxjs';
import { tap } from 'rxjs/operators';
import { Logger } from '@lov3kaizen/agentsea-core';
@Injectable()
export class LoggingInterceptor implements NestInterceptor {
private readonly logger = new Logger();
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
const request = context.switchToHttp().getRequest();
const startTime = Date.now();
return next.handle().pipe(
tap(() => {
const duration = Date.now() - startTime;
this.logger.info('Request completed', {
path: request.url,
method: request.method,
duration,
});
}),
);
}
}
// app.module.ts
import { APP_INTERCEPTOR } from '@nestjs/core';
@Module({
providers: [
{
provide: APP_INTERCEPTOR,
useClass: LoggingInterceptor,
},
],
})
export class AppModule {}WebSocket Integration
Stream agent responses using WebSockets:
typescript
// chat.gateway.ts
import {
WebSocketGateway,
WebSocketServer,
SubscribeMessage,
MessageBody,
} from '@nestjs/websockets';
import { Server } from 'socket.io';
import { Agent } from '@lov3kaizen/agentsea-core';
@WebSocketGateway()
export class ChatGateway {
@WebSocketServer()
server: Server;
constructor(private readonly agent: Agent) {}
@SubscribeMessage('chat')
async handleChat(
@MessageBody() data: { userId: string; message: string },
) {
const stream = await this.agent.stream(data.message, {
conversationId: data.userId,
sessionData: { userId: data.userId },
history: [],
});
for await (const chunk of stream) {
if (chunk.type === 'content') {
this.server.emit(`chat:${data.userId}`, {
type: 'content',
content: chunk.content,
});
} else if (chunk.type === 'tool_call') {
this.server.emit(`chat:${data.userId}`, {
type: 'tool_call',
toolName: chunk.toolName,
});
}
}
this.server.emit(`chat:${data.userId}`, { type: 'done' });
}
}Testing
Unit Tests
typescript
// chat.service.spec.ts
import { Test, TestingModule } from '@nestjs/testing';
import { ChatService } from './chat.service';
import { Agent, ToolRegistry, BufferMemory } from '@lov3kaizen/agentsea-core';
describe('ChatService', () => {
let service: ChatService;
let mockAgent: jest.Mocked<Agent>;
beforeEach(async () => {
mockAgent = {
execute: jest.fn(),
} as any;
const module: TestingModule = await Test.createTestingModule({
providers: [
ChatService,
{
provide: Agent,
useValue: mockAgent,
},
{
provide: ToolRegistry,
useValue: new ToolRegistry(),
},
{
provide: BufferMemory,
useValue: new BufferMemory(50),
},
],
}).compile();
service = module.get<ChatService>(ChatService);
});
it('should handle chat messages', async () => {
mockAgent.execute.mockResolvedValue({
content: 'Hello!',
toolCalls: [],
metadata: {},
});
const result = await service.chat('user-123', 'Hi');
expect(result.content).toBe('Hello!');
expect(mockAgent.execute).toHaveBeenCalled();
});
});E2E Tests
typescript
// chat.e2e-spec.ts
import { Test, TestingModule } from '@nestjs/testing';
import { INestApplication } from '@nestjs/common';
import * as request from 'supertest';
import { AppModule } from '../src/app.module';
describe('ChatController (e2e)', () => {
let app: INestApplication;
beforeAll(async () => {
const moduleFixture: TestingModule = await Test.createTestingModule({
imports: [AppModule],
}).compile();
app = moduleFixture.createNestApplication();
await app.init();
});
afterAll(async () => {
await app.close();
});
it('/chat/:userId (POST)', () => {
return request(app.getHttpServer())
.post('/chat/user-123')
.send({ message: 'Hello' })
.expect(201)
.expect((res) => {
expect(res.body).toHaveProperty('content');
expect(res.body).toHaveProperty('metadata');
});
});
});Production Deployment
Health Checks
typescript
// health.controller.ts
import { Controller, Get } from '@nestjs/common';
import { HealthCheck, HealthCheckService } from '@nestjs/terminus';
import { MCPRegistry } from '@lov3kaizen/agentsea-core';
@Controller('health')
export class HealthController {
constructor(
private health: HealthCheckService,
private mcpRegistry: MCPRegistry,
) {}
@Get()
@HealthCheck()
check() {
return this.health.check([
async () => {
const clients = this.mcpRegistry.getClients();
const connected = clients.every(c => c.isConnected());
return {
mcp: {
status: connected ? 'up' : 'down',
servers: clients.length,
},
};
},
]);
}
}Graceful Shutdown
typescript
// main.ts
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { MCPRegistry } from '@lov3kaizen/agentsea-core';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
// Enable graceful shutdown
app.enableShutdownHooks();
// Cleanup on shutdown
app.get(MCPRegistry).on('beforeShutdown', async () => {
const mcpRegistry = app.get(MCPRegistry);
await mcpRegistry.disconnectAll();
});
await app.listen(3000);
}
bootstrap();Best Practices
- Dependency Injection: Use NestJS DI for all AgentSea components
- Configuration: Use ConfigModule for environment-specific settings
- Error Handling: Implement global exception filters
- Validation: Use class-validator for DTO validation
- Guards: Implement authentication and authorization guards
- Testing: Write comprehensive unit and e2e tests
- Monitoring: Integrate with monitoring services
- Documentation: Use Swagger/OpenAPI for API documentation