@agentforge-io/nest
v3.0.1
Published
NestJS bindings for AgentForge — `AgentForgeModule.forRoot/forRootAsync` plus controllers, guards and decorators. Wraps @agentforge-io/core.
Readme
AgentForge
Framework NestJS para construir micro-SaaS de Agents as a Service usando Claude Agent SDK.
AgentForge abstrae toda la complejidad de crear un sistema multi-agente con monetización, persistencia de conversaciones, procesamiento en background y billing con Stripe — para que te puedas enfocar solo en definir tus agentes y herramientas.
Características
- Multi-agente con orquestación automática — Un agente orquestador puede delegar tareas a subagentes especializados. Claude decide cuándo y cómo delegar.
- Persistencia de conversaciones en Postgres — Historial completo con TypeORM, incluyendo tool calls y uso de tokens.
- Queue de tareas async con BullMQ + Redis — Procesa conversaciones largas en background, el cliente hace polling o recibe el resultado cuando está listo.
- Billing con Stripe incluido — Planes freemium, suscripciones mensuales/anuales, pay-per-use y créditos. Un adaptador para cambiar de proveedor de pagos.
- Tracking de uso por usuario — Límites de requests y tokens por plan, aplicados automáticamente.
- Framework HTTP: NestJS — DI, decoradores, guards, SSE streaming, Swagger.
- TypeScript nativo — Tipado completo, autocompletado total.
Instalación
npm install agentforge @nestjs/common @nestjs/core @nestjs/platform-express reflect-metadata rxjsRequerimientos:
- Node.js ≥ 18
- PostgreSQL ≥ 14
- Redis ≥ 6 (solo si usas queue)
Quickstart (5 minutos)
1. Configurar el módulo raíz
// app.module.ts
import { Module } from '@nestjs/common';
import { AgentForgeModule } from 'agentforge';
@Module({
imports: [
AgentForgeModule.forRoot({
anthropic: {
apiKey: process.env.ANTHROPIC_API_KEY!,
},
database: {
url: process.env.DATABASE_URL!,
},
agents: [
{
id: 'assistant',
name: 'Assistant',
systemPrompt: 'You are a helpful assistant.',
},
],
}),
],
})
export class AppModule {}2. Arrancar la app
// main.ts
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
async function bootstrap() {
const app = await NestFactory.create(AppModule, { rawBody: true });
await app.listen(3000);
}
bootstrap();3. Usar la API REST
AgentForge registra automáticamente las rutas:
# Crear una conversación
POST /conversations
{ "userId": "user_123", "agentId": "assistant" }
# Enviar un mensaje
POST /conversations/:id/messages
{ "userId": "user_123", "content": "Hello!" }
# Ver el historial
GET /conversations/:id?userId=user_123
# Listar conversaciones
GET /conversations?userId=user_123
# Stream de respuesta (Server-Sent Events)
GET /conversations/:id/stream?userId=user_123&content=HelloConfiguración completa
AgentForgeModule.forRoot({
// ─── Anthropic ─────────────────────────────────────────────────────────
anthropic: {
apiKey: 'sk-ant-...',
defaultModel: 'claude-opus-4-6', // Modelo por defecto para todos los agentes
defaultMaxTokens: 4096,
},
// ─── Base de datos ─────────────────────────────────────────────────────
database: {
url: 'postgresql://user:pass@localhost:5432/myapp',
autoMigrate: true, // Sincroniza el schema automáticamente (usa false en prod)
logging: false,
},
// ─── Redis (necesario para queue) ──────────────────────────────────────
redis: {
host: 'localhost',
port: 6379,
},
// ─── Queue async ───────────────────────────────────────────────────────
queue: {
enabled: true,
concurrency: 5,
},
// ─── Billing ───────────────────────────────────────────────────────────
billing: {
provider: 'stripe',
models: ['subscription', 'freemium'],
appUrl: 'https://myapp.com',
stripe: {
secretKey: 'sk_live_...',
webhookSecret: 'whsec_...',
},
plans: [
{
id: 'free',
name: 'Free',
isDefault: true,
limits: { requestsPerMonth: 50, tokensPerMonth: 25_000 },
},
{
id: 'pro',
name: 'Pro',
stripePriceId: 'price_xxx',
priceInCents: 1900,
interval: 'month',
limits: { requestsPerMonth: 1000, tokensPerMonth: 1_000_000 },
features: ['All agents', 'Priority support'],
},
],
},
// ─── Agentes ───────────────────────────────────────────────────────────
agents: [
{
id: 'assistant',
name: 'General Assistant',
systemPrompt: 'You are a helpful assistant.',
model: 'claude-opus-4-6',
tools: ['calculator', 'web_search'],
},
],
})Multi-Agente con Orquestación
Define un agente orquestador con canOrchestrate: true y lista sus subagentes. AgentForge inyecta automáticamente herramientas de delegación para que Claude sepa cuándo llamar a cada especialista.
agents: [
// Especialistas
{
id: 'researcher',
name: 'Research Specialist',
systemPrompt: 'You are an expert researcher. Find and summarize information.',
tools: ['web_search'],
},
{
id: 'writer',
name: 'Content Writer',
systemPrompt: 'You are a professional writer. Create high-quality content.',
},
{
id: 'analyst',
name: 'Data Analyst',
systemPrompt: 'You analyze data and provide insights.',
tools: ['calculator', 'query_data'],
},
// Orquestador — coordina a los especialistas
{
id: 'orchestrator',
name: 'AI Director',
systemPrompt: `You coordinate specialist agents for complex tasks.
Delegate research to the researcher, writing to the writer, and analysis to the analyst.
Synthesize all results into a final comprehensive answer.`,
canOrchestrate: true,
subAgents: ['researcher', 'writer', 'analyst'],
},
]Cuando el usuario envía un mensaje al orquestador, Claude puede llamar a delegate_to_researcher, delegate_to_writer, etc. automáticamente.
Definir Tools
Opción 1: Función defineTool (recomendada)
import { defineTool, AgentForgeModule } from 'agentforge';
const weatherTool = defineTool({
name: 'get_weather',
description: 'Get current weather for a city',
inputSchema: {
type: 'object',
properties: {
city: { type: 'string', description: 'City name' },
},
required: ['city'],
},
// Opcional: restringir a agentes específicos
agents: ['assistant', 'orchestrator'],
execute: async ({ city }, context) => {
// context tiene: userId, conversationId, agentId, messageId
const data = await fetchWeatherAPI(city as string);
return `${city}: ${data.temp}°C, ${data.condition}`;
},
});
// Registrar en el módulo
AgentForgeModule.forRootAsync({
useFactory: (config: ConfigService) => ({
agents: [...],
...
}),
options: {
tools: [weatherTool, calculatorTool, ...],
},
})Opción 2: Registro manual en tiempo de ejecución
import { ToolRegistryService } from 'agentforge';
@Injectable()
export class MyService {
constructor(private readonly toolRegistry: ToolRegistryService) {}
onModuleInit() {
this.toolRegistry.register({
name: 'my_tool',
description: '...',
inputSchema: { type: 'object', properties: {} },
execute: async (input) => 'result',
});
}
}Billing
API de Billing
# Ver planes disponibles
GET /billing/plans
# Crear sesión de checkout (Stripe Checkout)
POST /billing/checkout
{ "userId": "user_123", "planId": "pro" }
→ { "url": "https://checkout.stripe.com/..." }
# Portal de cliente (gestionar suscripción)
POST /billing/portal
{ "userId": "user_123" }
→ { "url": "https://billing.stripe.com/..." }
# Ver estado de billing del usuario
GET /billing/overview/user_123
# Cancelar suscripción
DELETE /billing/subscription/user_123
# Webhook de Stripe
POST /billing/webhook (con header stripe-signature)Agregar créditos
billing: {
models: ['credits'],
credits: {
tokensPerCredit: 1000, // 1 crédito = 1000 tokens
creditsPerRequest: 1, // +1 crédito por request
initialCredits: 100, // Créditos iniciales al registrarse
},
}Adaptador personalizado (PayPal, Paddle, LemonSqueezy, etc.)
import { IBillingAdapter } from 'agentforge';
export class PaddleAdapter implements IBillingAdapter {
async createCheckoutSession(params) { ... }
async createSubscription(params) { ... }
async cancelSubscription(id, atPeriodEnd) { ... }
async handleWebhook(payload, signature) { ... }
async getPortalUrl(customerId, returnUrl) { ... }
async createCustomer(params) { ... }
async getSubscription(id) { ... }
}
// En la config:
billing: {
provider: 'custom',
customAdapter: PaddleAdapter,
}Queue asíncrona
Envía mensajes en background y haz polling del resultado:
// Enviar mensaje asíncrono
POST /conversations/:id/messages
{ "userId": "user_123", "content": "...", "async": true }
→ { "jobId": "abc123", "estimatedWait": 5000 }
// Hacer polling
GET /jobs/abc123
→ { "status": "active", "progress": 70 }
→ { "status": "completed", "result": { "content": "..." } }Guards y decoradores
@RequirePlan() — Restricciones por plan
import { RequirePlan, UsageLimitGuard } from 'agentforge';
@Controller('conversations')
@UseGuards(UsageLimitGuard) // Aplica límites de uso automáticamente
export class MyController {
@Post()
@RequirePlan('pro', 'enterprise') // Solo usuarios en plan pro o enterprise
async createConversation(@Body() dto: CreateConversationDto) {
return this.agentService.createConversation(dto);
}
}UsageLimitGuard — Límites automáticos
Aplica el guard globalmente en main.ts:
import { UsageLimitGuard } from 'agentforge';
app.useGlobalGuards(app.get(UsageLimitGuard));Acceso a servicios internos
Todos los servicios se exportan para que puedas inyectarlos en tu propia lógica:
import {
AgentService,
BillingService,
ConversationService,
UsageService,
ToolRegistryService,
OrchestratorService,
} from 'agentforge';
@Injectable()
export class MyCustomService {
constructor(
private readonly agents: AgentService,
private readonly billing: BillingService,
private readonly usage: UsageService,
) {}
async onboardUser(userId: string, email: string) {
// Crear conversación de bienvenida
const { conversation } = await this.agents.createConversation({
userId,
agentId: 'assistant',
title: 'Welcome',
initialMessage: 'Hello! I am your AI assistant. How can I help you today?',
});
// Ver el uso
const summary = await this.usage.getSummary(userId);
console.log(`User ${userId}: ${summary.requestsUsed}/${summary.requestsLimit} requests used`);
return conversation;
}
}Variables de entorno
# Anthropic
ANTHROPIC_API_KEY=sk-ant-api03-...
# PostgreSQL
DATABASE_URL=postgresql://user:password@localhost:5432/myapp
# Redis
REDIS_HOST=localhost
REDIS_PORT=6379
# Stripe
STRIPE_SECRET_KEY=sk_test_...
STRIPE_WEBHOOK_SECRET=whsec_...
# App
APP_URL=http://localhost:3000Estructura de la base de datos
AgentForge crea automáticamente las siguientes tablas (prefijo af_):
| Tabla | Descripción |
|---|---|
| af_users | Usuarios con plan actual y balance de créditos |
| af_conversations | Sesiones de conversación con agentes |
| af_messages | Historial de mensajes con tool calls y uso de tokens |
| af_subscriptions | Suscripciones de Stripe por usuario |
| af_usage_records | Registro detallado de uso para billing |
Roadmap
- [ ] Soporte para MCP servers (Model Context Protocol)
- [ ] Adapter para LemonSqueezy y Paddle
- [ ] Panel de administración (React)
- [ ] Rate limiting por IP
- [ ] Webhooks de eventos para tu sistema (conversación iniciada, límite alcanzado, etc.)
- [ ] Soporte para archivos adjuntos en mensajes
- [ ] Streaming con WebSockets (además de SSE)
Licencia
MIT
