Plugin SDK
Extendé Replai con rutas + handlers de mensajes — paquetes npm.
El Plugin SDK te permite extender Replai con paquetes npm que agregan rutas HTTP, reaccionan a mensajes entrantes, y envían respuestas — todo dentro del scope autenticado y multi-tenant del API.
Anatomía de un plugin
Un plugin es un módulo Node que exporta por defecto un objeto ReplaiPlugin:
import type { ReplaiPlugin } from '@replai/plugin-host';
const plugin: ReplaiPlugin = {
name: 'echo',
version: '0.1.0',
description: 'Echoes WhatsApp messages prefixed with !echo back to the sender.',
permissions: ['inbound:read', 'outbound:send'],
setup(ctx, scope) {
// HTTP route — mounted at /v1/plugins/echo/ping
scope.get('/ping', () => ({ ok: true }));
// Reacciona a mensajes entrantes
ctx.onInbound(async (event) => {
if (event.type !== 'message.received') return;
if (!event.bodyPreview.startsWith('!echo ')) return;
await ctx.sendText({
sessionId: event.sessionId,
to: event.remoteJid,
text: event.bodyPreview.slice(6),
clientMessageId: `echo:${event.messageId}`,
});
});
},
};
export default plugin;Cómo se cargan
Los plugins se listan por nombre en la variable de entorno REPLAI_PLUGINS (separados por coma):
REPLAI_PLUGINS=@replai/plugin-echo,@replai/plugin-stats
Cada uno se resuelve vía import estándar de Node — funcionan paquetes de npm, workspace packages, o paths absolutos (file://). El loader corre una sola vez al boot del API; cambios requieren restart.
Config por plugin
Si tu plugin tiene config, pasala como JSON en una variable de entorno con la forma REPLAI_PLUGIN_<NAME>_CONFIG:
REPLAI_PLUGIN_ECHO_CONFIG={"emoji":"🦜"}Eso aparece en ctx.config dentro del setup. JSON inválido → fallback a {}.
Lo que tu plugin recibe
El argumento ctx (ReplaiPluginContext) expone una superficie chica y estable:
| Campo | Para qué |
|---|---|
pluginName | Nombre estable del plugin (matchea el prefix de ruta). |
logger | Pino-shaped logger child. Tag automático plugin: <name>. |
config | Tu config parseada del env. |
onInbound(handler) | Subscribe a eventos message.received, message.sent, message.delivered, message.read. Filtrar por tenant en tu handler si hace falta. |
sendText(args) | Envía un mensaje saliente. Internamente encola en el outbound queue — mismo path que POST /v1/messages. |
El scope que recibís es una FastifyInstance ya montada en /v1/plugins/<name>, detrás de la auth con API key. En cada handler tenés disponibles request.tenantId, request.apiKey, y request.db (Prisma scoped al tenant).
Inspeccionar plugins cargados
GET /v1/plugins/manifest
{
"count": 1,
"plugins": [
{
"name": "echo",
"version": "0.1.0",
"description": "Echoes WhatsApp messages...",
"permissions": ["inbound:read", "outbound:send"],
"spec": "@replai/plugin-echo",
"mountPath": "/v1/plugins/echo"
}
]
}Trust model
- Los plugins corren in-process con el API — tienen acceso completo a la base de datos y al sistema operativo del host. Cargá solo plugins que desarrollaste vos o partners verificados.
- El campo
permissionses documental hoy — declarás qué necesitás, pero no hay enforcement en runtime. Próxima iteración (Sprint 25) va a agregar gating por capability. - Errores en
setup()o en route handlers se capturan + loggean como warning. Un plugin que crashea no derriba al resto.
Próximos pasos
- Cloná
@replai/plugin-echocomo template. - Publicalo en npm como private package (o usalo desde un workspace en el mismo monorepo).
- Agregalo a
REPLAI_PLUGINSen tu deployment y restartea el API. - Verificá con
GET /v1/plugins/manifestque cargó correctamente.