npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@dupply/sdk

v3.0.0

Published

Platform-agnostic AI agent SDK for chat applications — WhatsApp (via WAHA), Telegram, Messenger, or any custom channel.

Readme

Dupply SDK (@dupply/sdk)

Construa agentes de IA em qualquer plataforma de chat, WhatsApp, Telegram, Messenger, webchat ou canal interno, sem reescrever sua lógica de negócio.

npm install @dupply/sdk

O SDK é agnóstico ao canal e agnóstico ao LLM. O núcleo (engine, memória, tools, RAG, roteamento) não conhece nenhuma plataforma específica. Você liga o canal por um IAdapter e o modelo por um ILLMProvider. O único adapter de canal embutido é o WahaAdapter (WhatsApp via WAHA), mas qualquer chat funciona escrevendo um adapter pequeno.


Glossário (peças do SDK)

| Peça | O que é | Interface | Embutidos | |------|---------|-----------|-----------| | Adapter | Transporte de chat. Envia/recebe mensagens de uma plataforma. | IAdapter | WahaAdapter (WhatsApp/WAHA), ConsoleAdapter (terminal) | | Provider | Provedor de LLM. Gera respostas. | ILLMProvider | OpenAIProvider, GeminiProvider | | Engine | Orquestra o pipeline: memória, intenção, RAG, tools, áudio, envio. | DupplyEngine | núcleo | | Memory | Histórico de conversa por usuário. | IMemory | InMemoryMemory (default), RedisMemory | | KnowledgeBase | Documentos para RAG. | IKnowledgeBase | SimpleVectorKnowledgeBase | | Tool | "Habilidade" que o LLM pode chamar (function calling). | ITool | você define | | Client | Fachada que recebe webhooks e despacha para as engines. | DupplyClient | núcleo |

Resumindo: Adapter = por onde a conversa entra e sai, Provider = quem pensa, Engine = quem coordena, Memory/KnowledgeBase/Tool = recursos que a engine usa.


Instalação

npm install @dupply/sdk

Pré-requisitos: Node.js >= 18, TypeScript >= 5. O pacote é ESM ("type": "module") e também publica um build CJS.

Dependências de infra são opcionais (peerDependencies): o SDK só precisa de openai, @google/genai e zod (já incluídos). Para usar recursos opcionais, instale o que for usar:

| Recurso | Pacote a instalar | |---------|-------------------| | Memória persistente em Redis (RedisMemory) | redis | | Fila / deduplicação distribuída (QueueService) | bullmq + ioredis | | Servidor HTTP de exemplo (Express) | express (+ cors se quiser) |

A memória padrão é in-memory (InMemoryMemory), não requer Redis.


Quickstart mínimo (roda no terminal, sem WhatsApp)

Este exemplo usa o ConsoleAdapter: você digita no terminal e a IA responde. Não precisa de WAHA nem Redis, só uma chave da OpenAI. Salve como quickstart.ts e rode com npx tsx quickstart.ts.

import {
  DupplyEngine,
  OpenAIProvider,
  ConsoleAdapter,
  type ITool,
} from '@dupply/sdk';

// 1. Provider de LLM (implementa ILLMProvider)
const llm = new OpenAIProvider(process.env.OPENAI_API_KEY!, 'gpt-4o-mini');

// 2. Adapter de canal (implementa IAdapter). ConsoleAdapter = stdin/stdout.
const adapter = new ConsoleAdapter({ botLabel: 'Assistente' });

// 3. Engine. provider:'openai' liga o LLM real (default é 'mock').
const engine = new DupplyEngine(adapter, llm, {
  provider: 'openai',
  apiKey: process.env.OPENAI_API_KEY!, // necessário p/ tools + RAG + IntentRouter
  systemPrompt: 'Você é um assistente prestativo. Responda de forma curta.',
});

// 4. Uma tool simples (function calling). O LLM decide quando chamar.
const horasTool: ITool = {
  name: 'get_current_time',
  description: 'Retorna a hora atual. Use quando perguntarem que horas são.',
  parameters: { type: 'object', properties: {}, required: [] },
  async execute() {
    return { now: new Date().toLocaleTimeString('pt-BR') };
  },
};
engine.registerTool(horasTool);

// 5. Escuta o adapter (modo polling). Digite uma mensagem e tecle Enter.
await engine.listen();

Pontos importantes confirmados no código:

  • O construtor da engine é new DupplyEngine(adapter, llm, config, memory?).
  • provider no config default é 'mock' (responde uma string fixa de eco). Use 'openai' para acionar o llm de verdade.
  • apiKey no config habilita os subsistemas que usam OpenAI diretamente (IntentRouter e ContextOrchestrator). Com OpenAIProvider, o cliente HTTP é reusado automaticamente, então passar apiKey é o caminho recomendado.
  • engine.listen() consome o canal via polling (bom para ConsoleAdapter/dev). Em produção com webhooks, use o DupplyClient (abaixo).

Quickstart de produção (WhatsApp via WAHA + webhook)

import express from 'express';
import {
  DupplyClient,
  DupplyEngine,
  OpenAIProvider,
  WahaAdapter,
} from '@dupply/sdk';

// 1. Adapter WhatsApp via WAHA. Note: usa `session`, NÃO `instance`.
const adapter = new WahaAdapter({
  apiUrl: 'http://localhost:3000',
  apiKey: process.env.WAHA_API_KEY!,
  session: 'default',
});

// 2. Provider
const llm = new OpenAIProvider(process.env.OPENAI_API_KEY!, 'gpt-4o-mini');

// 3. Engine
const engine = new DupplyEngine(adapter, llm, {
  provider: 'openai',
  apiKey: process.env.OPENAI_API_KEY!,
  systemPrompt: 'Você é um atendente da Empresa X.',
});

// 4. Client (recebe webhooks e despacha para a engine)
const client = new DupplyClient(adapter);
client.registerEngine(engine);

// 5. Webhook. Funciona com Express, Fastify, Hono, Next.js, etc.
const app = express();
app.use(express.json());
app.post('/webhook', async (req, res) => {
  await client.handleWebhook(req.body);
  res.json({ ok: true });
});
app.listen(3001);

Para validar a assinatura HMAC do webhook (recomendado em produção), passe webhookSecurity ao DupplyClient e use client.handleWebhookSecure(rawBody, headers). Veja docs/14-errors.md e o handler em docs/01-architecture.md.


Trocar de canal ou de modelo é só trocar uma linha

// Canal: terminal (dev) → WhatsApp (prod) → seu canal custom
const a1 = new ConsoleAdapter();
const a2 = new WahaAdapter({ apiUrl, apiKey, session: 'default' });
const a3 = new MeuTelegramAdapter(token); // implemente IAdapter

// Modelo: OpenAI → Gemini → seu provider custom (ex: Claude)
const m1 = new OpenAIProvider('sk-...', 'gpt-4o-mini');
const m2 = new GeminiProvider('AIza...', 'gemini-2.5-flash');
const m3 = new MeuClaudeProvider('sk-ant-...'); // implemente ILLMProvider

const engine = new DupplyEngine(a2, m2, { provider: 'openai', apiKey: 'sk-...' });

A lógica de IA, tools, memória e RAG não muda. Veja docs/02-adapters.md (adapter custom) e docs/03-llm-providers.md (provider custom).


Recursos

Tools (Function Calling)

engine.registerTool({
  name: 'consultar_pedido',
  description: 'Verifica status de um pedido. Use quando o cliente perguntar sobre o pedido.',
  parameters: {
    type: 'object',
    properties: { pedido_id: { type: 'string' } },
    required: ['pedido_id'],
  },
  async execute({ pedido_id }, ctx) {
    return await db.getOrder(pedido_id as string); // string ou objeto serializável
  },
});

O ctx é um ToolContext com senderId, channelId e message. Veja docs/06-tools.md.

RAG (Base de Conhecimento)

import { SimpleVectorKnowledgeBase } from '@dupply/sdk';

const kb = new SimpleVectorKnowledgeBase();
await kb.addDocument('Política de reembolso: 7 dias corridos a partir da compra.', { source: 'faq' });

const engine = new DupplyEngine(adapter, llm, {
  provider: 'openai',
  apiKey: 'sk-...',
  knowledgeBase: kb,
});

A interface é addDocument(content, metadata?) / search(query, topK?) / clear(). Veja docs/07-knowledge-base.md.

Memória

import { RedisMemory } from '@dupply/sdk'; // requer `npm install redis`

// In-memory é o default (basta não passar memory):
const engineDev = new DupplyEngine(adapter, llm, config);

// Redis (produção): construtor (redisUrl, options?)
const memory = new RedisMemory('redis://localhost:6379', { ttlSeconds: 86400, maxMessages: 20 });
await memory.connect();
const enginePrd = new DupplyEngine(adapter, llm, config, memory);

Veja docs/05-memory.md.

Multi-tenancy, um bot, múltiplas personas

engine
  .registerInstance({ instanceId: 'loja_sp', systemPrompt: 'Atendente da Loja SP.' })
  .registerInstance({ instanceId: 'loja_rj', systemPrompt: 'Atendente da Loja RJ.', tools: [toolRJ] });
// Roteamento automático pelo channelId da mensagem

Veja docs/08-multi-tenancy.md.

Token Tracking, saiba quanto cada canal custa

import { UsageTracker } from '@dupply/sdk';

const tracker = new UsageTracker();
tracker.onTokensConsumed(e => {
  console.log(`${e.instanceId}: ${e.totalTokens} tokens, $${e.costUsd?.toFixed(6)}`);
});
const llm = new OpenAIProvider('sk-...', 'gpt-4o', { usageTracker: tracker });

Veja docs/10-token-tracking.md.

Transcrição de áudio (Whisper)

// Automático no OpenAIProvider: mensagens de voz são transcritas (whisper-1) antes do pipeline.
const llm = new OpenAIProvider('sk-...'); // transcribeAudio já incluído

O GeminiProvider não implementa transcribeAudio, então áudios são ignorados quando ele é o provider.

Structured Outputs (Zod)

import { z } from 'zod';

const LeadSchema = z.object({
  nome: z.string(),
  interesse: z.enum(['basico', 'pro', 'enterprise']),
  message: z.string(), // se o schema tiver `message` (ou `resposta`), ele é enviado ao usuário
});

engine.registerInstance({ instanceId: 'captacao', outputSchema: LeadSchema });

const engine = new DupplyEngine(adapter, llm, {
  provider: 'openai',
  apiKey: 'sk-...',
  onStructuredOutput: (data, meta) => saveLead(data, meta.userId), // tipado, validado
});

Veja docs/11-structured-outputs.md.


Escrever seu próprio adapter (qualquer canal)

Estenda BaseAdapter (que já gerencia o registro de handlers) e implemente os métodos de envio:

import { BaseAdapter, type MediaOptions } from '@dupply/sdk';

export class MeuWebchatAdapter extends BaseAdapter {
  readonly name = 'MeuWebchatAdapter';

  async initialize(): Promise<void> { /* conectar à plataforma */ }

  async sendText(to: string, text: string): Promise<void> {
    await minhaPlataforma.send(to, text);
  }
  async sendMedia(to: string, media: string | Buffer, options?: MediaOptions): Promise<void> {
    await minhaPlataforma.sendFile(to, media, options?.caption);
  }
  async sendPoll(to: string, name: string, options: string[], multiple?: boolean): Promise<void> {
    await minhaPlataforma.sendPoll(to, name, options, multiple);
  }

  // Quando uma mensagem chega da sua plataforma, normalize e emita:
  onIncoming(raw: any): void {
    this.emitMessage({
      id: raw.messageId,
      from: raw.userId,
      text: raw.text,
      timestamp: Math.floor(Date.now() / 1000),
    });
  }
}

getMediaAsBase64?(messageRaw) é opcional, implemente apenas se quiser visão/áudio. Veja o contrato completo e um exemplo de Telegram em docs/02-adapters.md.


Documentação

| Guia | Conteúdo | |------|---------| | Arquitetura | Fluxo completo, tipos centrais, camadas | | Adapters | IAdapter, WahaAdapter, ConsoleAdapter, adapter custom | | LLM Providers | OpenAIProvider, GeminiProvider, provider custom | | Engine | Pipeline, EngineOptions, callbacks, multi-tenancy | | Memória | InMemoryMemory, RedisMemory, interface IMemory | | Tools | ITool, ToolRegistry, ToolContext | | Knowledge Base | RAG, SimpleVectorKnowledgeBase, custom | | Multi-tenancy | registerInstance(), isolamento por canal | | Intent Router | Roteamento de baixo custo | | Token Tracking | UsageTracker, billing | | Structured Outputs | Zod schemas, strict mode | | Fila | BullMQ, deduplicação distribuída | | Connection Watcher | Reconexão automática, heartbeat | | Erros | Tipos de erro, retry, tratamento |


Licença

MIT, veja LICENSE.