VoltarTake Sales

SDK JavaScript

SDK para integrar agentes Take Sales em interfaces customizadas via WebSocket.

Introdução

O SDK permite integrar agentes Take Sales em qualquer interface customizada. Conecte-se via WebSocket e construa sua própria interface de chat.

ℹ️

Para casos simples, o widget embutido é a forma mais fácil de adicionar um agente ao seu site — basta colar um snippet, sem escrever código. Use o SDK quando precisar de controle total sobre a interface e a experiência do usuário.

Instalação

npm install @takesales/sdk
⚠️

Esta página descreve o @takesales/sdk 0.1.33, a versão publicada hoje. Ela tem limitações conhecidas:

  • o hook useTakeSalesAgent duplica o texto de cada resposta do agente ("Olá" aparece como "OláOlá"). Até a próxima versão, use o TakeSalesClient e trate o streaming como em Streaming de texto;
  • mensagens de atendente humano (handoff), troca de agente e encerramento da conversa pelo servidor não chegam ao SDK. Se o agente tem transferência para humano habilitada, use o widget embutido;
  • o data de rich content e o evento toolConfirmation chegam num formato diferente das typings do pacote. As seções Rich Content e Tool Confirmation mostram o formato real.

Quick Start (React)

import { useTakeSalesAgent } from '@takesales/sdk/react'
 
function Chat() {
  const { messages, sendMessage, isConnected, agentConfig } = useTakeSalesAgent({
    agentId: 'your-agent-uuid',
    wsUrl: 'wss://api-agent.takesales.ai/ws',
  })
 
  if (!isConnected) return <div>Connecting...</div>
 
  return (
    <div>
      <h2>{agentConfig?.agentName}</h2>
      {messages.map((msg) => (
        <div key={msg.id} className={msg.role}>
          <p>{msg.text}</p>
          {msg.richContent?.type === 'carousel' && (
            <MyCarousel cards={msg.richContent.data} />
          )}
          {msg.richContent?.type === 'quick_replies' && (
            <QuickReplies options={richContentData(msg.richContent)} onSelect={sendMessage} />
          )}
        </div>
      ))}
      <input
        placeholder="Type a message..."
        onKeyDown={(e) => {
          if (e.key === 'Enter') {
            sendMessage(e.currentTarget.value)
            e.currentTarget.value = ''
          }
        }}
      />
    </div>
  )
}

Quick Start (Vanilla JS/TypeScript)

import { TakeSalesClient } from '@takesales/sdk'
 
const client = new TakeSalesClient({
  agentId: 'your-agent-uuid',
  wsUrl: 'wss://api-agent.takesales.ai/ws',
})
 
client.on('connected', () => {
  console.log('Agent ready')
  client.sendText('Hello!')
})
 
client.on('agentConfig', (config) => {
  console.log('Agent:', config.agentName)
})
 
client.on('message', (text, endOfTurn) => {
  // endOfTurn = true carries the whole turn again: replace, don't append
  if (endOfTurn) console.log('Agent says:', text)
})
 
client.on('richContent', (content) => {
  console.log('Rich content:', content.type, richContentData(content))
})
 
client.on('error', (err) => {
  console.error('Error:', err)
})
 
client.on('disconnect', (code, reason) => {
  console.log('Disconnected:', code, reason)
})
 
client.connect()

API Reference

TakeSalesClient

Client WebSocket framework-agnostic.

Constructor

new TakeSalesClient(config: TakeSalesClientConfig)
ParameterTypeRequiredDescription
agentIdstringSimUUID do agente
wsUrlstringSimURL do servidor WebSocket
visitorobjectNãoInformações do visitante (veja abaixo)
conversationIdstringNãoRetomar uma sessão anterior
isTestbooleanNãoMarcar como sessão de teste
clientVariablesRecord<string, string>NãoVariáveis de cliente pré-resolvidas para enriquecimento de ferramentas
clientVariableConfigsClientVariableConfig[]NãoResolução automática de variáveis do browser (localStorage, cookies, etc.)
reconnectobjectNãoConfigurações de reconexão

Objeto visitor:

CampoTipoDescrição
emailstringEmail do visitante
namestringNome do visitante
phonestringTelefone do visitante
contactDataRecord<string, string>Campos customizados. Ignorado na conexão: envie com sendUpdateVisitor depois do sessionCreated (veja Com identificação do visitante)
visitorIdstringID persistente do visitante. Se omitido, o SDK gera um e guarda em localStorage

Objeto reconnect:

CampoTipoDefaultDescrição
enabledbooleantrueHabilitar auto-reconexão
maxAttemptsnumber5Máximo de tentativas
baseDelaynumber1000Delay base em ms (exponential backoff)

Methods

MétodoDescrição
connect()Abre conexão WebSocket
disconnect()Fecha conexão (sem auto-reconexão)
sendText(text)Envia uma mensagem de texto
sendFormSubmit(formId, data)Submete um formulário de lead
sendScheduleConfirm(scheduleId, date, time)Confirma um agendamento
sendToolConfirmation(confirmationId, confirmed)Aprova/rejeita uma ação de ferramenta
sendUpdateVisitor(data)Atualiza informações do visitante (email, name, phone, contactData). Só funciona depois do sessionCreated
registerTool(name, tool)Registra uma client tool (veja Client tools). Chame antes de connect()
unregisterTool(name)Remove uma client tool

Properties

PropriedadeTipoDescrição
isConnectedbooleanEstado atual da conexão
conversationIdstring | nullID da sessão atual
agentConfigServerAgentConfig | nullConfiguração do agente recebida do servidor

Events

client.on('connected', () => void)
client.on('message', (text: string, endOfTurn: boolean) => void)
client.on('richContent', (content: RichContent) => void)
client.on('agentConfig', (config: ServerAgentConfig) => void)
client.on('sessionCreated', (data: { conversationId: string }) => void)
client.on('toolConfirmation', (data: ToolConfirmation) => void)
client.on('toolCall', (data: { name: string, args: Record<string, unknown>, callId?: string }) => void)
client.on('usageWarning', (data: { message: string, warningType: string }) => void)
client.on('error', (error: string) => void)
client.on('disconnect', (code?: number, reason?: string) => void)

Use client.off(event, listener) para remover um listener.

message chega uma vez por pedaço do streaming com endOfTurn = false e, no fim do turno, mais uma vez com endOfTurn = true trazendo o texto inteiro do turno. Concatene os pedaços ou use só o texto final, nunca os dois.

error também recebe recusas do servidor: agente sem permissão para a origin, workspace sem créditos (connection_error) e setup inválido.


useTakeSalesAgent (React Hook)

import { useTakeSalesAgent } from '@takesales/sdk/react'

Config

Aceita todos os campos de TakeSalesClientConfig mais:

ParameterTypeDefaultDescription
autoConnectbooleantrueConectar ao montar o componente

Return Value

CampoTipoDescrição
messagesTakeSalesMessage[]Todas as mensagens (usuário + agente)
sendMessage(text: string) => voidEnvia uma mensagem (adiciona a messages automaticamente)
isConnectedbooleanEstado da conexão
agentConfigServerAgentConfig | nullConfiguração do agente
conversationIdstring | nullID da sessão
connect() => voidConectar manualmente
disconnect() => voidDesconectar manualmente
sendFormSubmit(formId, data) => voidSubmeter formulário de lead
sendScheduleConfirm(scheduleId, date, time) => voidConfirmar agendamento
sendToolConfirmation(confirmationId, confirmed) => voidAprovar/rejeitar ferramenta
registerTool(name, tool) => voidRegistrar uma client tool
unregisterTool(name) => voidRemover uma client tool

Aceita também tools: Record<string, LocalToolDefinition> na config para registrar client tools ao montar.

⚠️

No 0.1.33 o hook duplica o texto de toda resposta do agente e não expõe toolConfirmation, usageWarning nem error. Para produção, prefira o TakeSalesClient até a próxima versão.

TakeSalesMessage

interface TakeSalesMessage {
  id: string
  role: 'user' | 'agent'
  text: string
  richContent?: RichContent
  timestamp: Date
}

Rich Content Types

O agente pode enviar conteúdo rico junto com mensagens de texto. Cada tipo tem uma estrutura de dados específica.

⚠️

No 0.1.33, só o carousel chega com data no formato abaixo. Nos outros tipos o data vem com o envelope do servidor: { options } em quick_replies, { leadForm } em lead_form, { scheduleForm }, { inlineMap }, { photoGallery } e { comparator }. Use um helper que funcione nos dois formatos:

import type { RichContent } from '@takesales/sdk'
 
const ENVELOPE: Record<string, string> = {
  quick_replies: 'options',
  lead_form: 'leadForm',
  schedule_form: 'scheduleForm',
  inline_map: 'inlineMap',
  photo_gallery: 'photoGallery',
  comparator: 'comparator',
}
 
export function richContentData(content: RichContent): any {
  const data = content.data as any
  const key = ENVELOPE[content.type]
  return (key && data?.[key]) ?? data
}
⚠️

Os campos de rich content (URLs, títulos, imagens) podem vir de ferramentas customizadas, ou seja, de APIs de terceiros. Trate como dado não confiável: aceite só URLs https: em href e src e nunca injete o conteúdo como HTML.

{ type: 'carousel', data: CarouselCard[] }
 
interface CarouselCard {
  title: string
  url: string
  subtitle?: string
  description?: string
  imageUrl?: string
  price?: string
}

Quick Replies

{ type: 'quick_replies', data: QuickReplyOption[] }
 
interface QuickReplyOption {
  label: string
  value: string
}

Renderize botões e chame sendText(option.value) (vanilla) ou sendMessage(option.value) (React) no clique. No 0.1.33 o array está em data.options (use richContentData).

Lead Form

{ type: 'lead_form', data: LeadFormCard }
 
interface LeadFormCard {
  formId: string
  fields: Array<{
    key: string
    type: 'text' | 'email' | 'phone' | 'textarea'
    label: string
    placeholder?: string
    required: boolean
  }>
  submitLabel?: string
  successMessage?: string
}

Renderize um formulário e chame sendFormSubmit(formId, data) ao submeter.

Schedule Form

{ type: 'schedule_form', data: ScheduleFormCard }
 
interface ScheduleFormCard {
  availableSlots: Array<{ date: string; times: string[] }>
  propertyTitle?: string
  scheduleId?: string
}

Renderize um seletor de data/hora e chame sendScheduleConfirm(scheduleId, date, time).

Inline Map

{ type: 'inline_map', data: InlineMapCard }
 
interface InlineMapCard {
  lat: number
  lng: number
  address: string
  zoom?: number
  googleMapsApiKey?: string
}
{ type: 'photo_gallery', data: PhotoGalleryCard }
 
interface PhotoGalleryCard {
  images: string[]
  title?: string
}

Comparator

{ type: 'comparator', data: ComparatorCard }
 
interface ComparatorCard {
  title?: string
  // Columns: anything comparable (plans, products, listings)
  items?: Array<{ title: string; url?: string; imageUrl?: string; price?: string }>
  // Rows: a label plus one value per item, in `items` order
  attributes?: Array<{ label: string; values: string[] }>
  // Legacy real-estate shape sent by older agents
  properties?: Array<{
    title: string
    url?: string
    imageUrl?: string
    price?: string
    area?: string
    bedrooms?: number
    bathrooms?: number
    differentials?: string[]
  }>
}

Tool Confirmation

Quando uma tool tem nível de risco confirm, o agente pausa a execução e envia um evento de confirmação antes de executar. No widget embutido, um card de confirmação aparece automaticamente no chat. Se você usa o SDK, precisa ouvir o evento e montar sua própria UI.

{ type: 'tool_confirmation', data: ToolConfirmation }
 
// What the server sends
interface ToolConfirmationFrame {
  confirmationId: string
  functionName: string        // ex: "cancel_subscription"
  functionTitle: string       // nome amigável configurado na tool
  functionDescription: string // descrição da ação
  args: Record<string, unknown> // parâmetros que serão enviados
}
⚠️

As typings do 0.1.33 declaram ToolConfirmation como { confirmationId, toolName, message, details }, campos que o servidor não envia. Leia os campos acima com um cast. O hook React não expõe esse evento: use o TakeSalesClient.

Exemplo de uso:

client.on('toolConfirmation', (event) => {
  const data = event as unknown as ToolConfirmationFrame
 
  // Exiba um diálogo/card de confirmação na sua UI
  showConfirmDialog({
    title: data.functionTitle || data.functionName,
    message: data.functionDescription,
    params: data.args,
    onConfirm: () => client.sendToolConfirmation(data.confirmationId, true),
    onCancel: () => client.sendToolConfirmation(data.confirmationId, false),
  })
})

Se o visitante confirmar, a tool é executada normalmente. Se cancelar, o agente recebe um aviso e continua a conversa sem executar a ação.


Agent Config

Após conectar, você recebe a configuração do agente:

interface ServerAgentConfig {
  agentId: string
  workspaceId: string
  agentName: string
  greeting: string
  avatarUrl: string | null
  language: string
  enableVoice: boolean
  enableTextChat: boolean
  collectVisitorEmail: boolean
  collectVisitorName: boolean
  collectVisitorPhone: boolean
  quickReplies?: string[]
  primaryColor?: string
  textColor?: string
  backgroundColor?: string
  title?: string
  subtitle?: string
  showBranding?: boolean
  enableHandoff?: boolean     // true: o agente pode transferir para um humano (sem suporte no 0.1.33)
  typingEvents?: boolean      // true: o servidor agrupa mensagens seguidas do visitante numa resposta
  hasKnowledgeBase?: boolean
}

O objeto tem outros campos de uso do widget embutido (voz, formulário de contato, guardrails). As typings do 0.1.33 listam secondaryColor e conversationId, que o servidor não envia: o ID da conversa chega no sessionCreated.

Use para estilizar sua UI, exibir a saudação e configurar coleta de dados do visitante.


Client Variables

Client variables permitem que ferramentas autenticadas acessem dados do browser do visitante (localStorage, cookies, etc.). O SDK suporta três modos:

Automático (recomendado)

Quando nenhum clientVariables ou clientVariableConfigs é fornecido, o SDK busca automaticamente a configuração do agente via preflight e resolve as variáveis do browser — o mesmo comportamento do widget embutido.

const client = new TakeSalesClient({
  agentId: 'your-agent-uuid',
  wsUrl: 'wss://api-agent.takesales.ai/ws',
  // Client variables são resolvidas automaticamente via preflight config
})

Manual

Passe valores pré-resolvidos diretamente:

const client = new TakeSalesClient({
  agentId: 'your-agent-uuid',
  wsUrl: 'wss://api-agent.takesales.ai/ws',
  clientVariables: {
    auth_token: localStorage.getItem('token') ?? '',
    user_id: '12345',
  },
})

Baseado em configuração

Passe configs de extração (mesmo formato do dashboard):

import { type ClientVariableConfig } from '@takesales/sdk'
 
const client = new TakeSalesClient({
  agentId: 'your-agent-uuid',
  wsUrl: 'wss://api-agent.takesales.ai/ws',
  clientVariableConfigs: [
    { name: 'auth_token', source: 'localStorage', key: 'token' },
    { name: 'user_id', source: 'cookie', key: 'uid' },
  ],
})

Sources suportadas: localStorage, sessionStorage, cookie, window, querySelector, meta.

Se ambos clientVariables e clientVariableConfigs forem fornecidos, os valores manuais têm precedência.

⚠️

No modo automático, quem decide o que o SDK lê do navegador (chaves de localStorage, cookies sem HttpOnly, caminhos em window) é a configuração do agente no dashboard. Se o seu site guarda tokens no navegador, prefira o modo manual ou o baseado em configuração, com a lista explícita do que enviar. Variável que carrega token ou sessão precisa ser marcada como secreta no agente: variável não secreta entra no contexto do modelo.


Connection Lifecycle

connect()
  |
  v
Preflight fetch (auto-discovers client variable configs)
  |
  v
WebSocket open
  |
  v
Service setup sent (agent_id + visitor info + client variables)
  |
  v
Server sends: agentConfig  -->  'agentConfig' event
Server sends: sessionCreated  -->  'sessionCreated' event
  |
  v
Session setup sent (text-only mode)
  |
  v
Server sends: setupComplete  -->  'connected' event
  |
  v
Ready to send/receive messages
  |
  v
disconnect() or connection lost
  |
  v
Auto-reconnect (exponential backoff, up to 5 attempts)

Segurança

A autenticação usa o mesmo modelo do Google Maps ou Stripe.js:

  • agentId é um identificador público (UUID) embutido no código do cliente
  • Validação de origin é feita server-side via lista de origins permitidas do agente
  • Configure origins permitidas no dashboard em Configurações do Agente

Nenhuma API key ou bearer token é necessária para uso client-side.

Dados que o SDK envia

Ao conectar, o 0.1.33 envia ao servidor, sem opção para desligar:

  • um ID persistente do visitante, gravado em localStorage (takesales_visitor_id);
  • dados do navegador para identificar o visitante: resolução e color depth da tela, fuso, idiomas, plataforma, número de núcleos e devicePixelRatio;
  • a URL completa da página (location.href, com query string e fragmento) e o document.referrer.

Se o seu site exige consentimento antes desse tipo de coleta (LGPD/GDPR), só chame connect() (ou monte o hook com autoConnect: true) depois do consentimento. Evite conectar em páginas cuja URL carrega tokens ou dados pessoais, como callback de OAuth e link de redefinição de senha.

O ID da conversa dá acesso ao histórico dela. Guarde-o como guardaria um identificador de sessão.


Exemplos

Streaming de texto

let current = ''     // text of the turn being streamed
let typing = false
 
client.on('message', (text, endOfTurn) => {
  if (endOfTurn) {
    current = text   // full turn: replace, don't append
    typing = false
  } else {
    current += text
    typing = true
  }
  render(current, typing)
})

Com o debounce de respostas ligado no agente, várias mensagens seguidas do visitante recebem uma resposta só.

Handling quick replies

function Chat() {
  const { messages, sendMessage } = useTakeSalesAgent({ ... })
 
  return (
    <div>
      {messages.map((msg) => (
        <div key={msg.id}>
          <p>{msg.text}</p>
          {msg.richContent?.type === 'quick_replies' && (
            <div className="flex gap-2">
              {richContentData(msg.richContent).map((opt: QuickReplyOption) => (
                <button key={opt.value} onClick={() => sendMessage(opt.value)}>
                  {opt.label}
                </button>
              ))}
            </div>
          )}
        </div>
      ))}
    </div>
  )
}

Retomando uma sessão

function Chat() {
  const [savedId] = useState(() => localStorage.getItem('conversationId'))
 
  const { messages, sendMessage, conversationId } = useTakeSalesAgent({
    agentId: 'your-agent-uuid',
    wsUrl: 'wss://api-agent.takesales.ai/ws',
    conversationId: savedId ?? undefined,
  })
 
  useEffect(() => {
    if (conversationId) localStorage.setItem('conversationId', conversationId)
  }, [conversationId])
 
  return <div>...</div>
}

Com identificação do visitante

const { messages, sendMessage } = useTakeSalesAgent({
  agentId: 'your-agent-uuid',
  wsUrl: 'wss://api-agent.takesales.ai/ws',
  visitor: {
    email: user.email,
    name: user.name,
    phone: user.phone,
  },
})

Com client variables (React)

const { messages, sendMessage } = useTakeSalesAgent({
  agentId: 'your-agent-uuid',
  wsUrl: 'wss://api-agent.takesales.ai/ws',
  clientVariables: {
    auth_token: localStorage.getItem('token') ?? '',
  },
})

Retomando uma sessão (Vanilla JS)

const savedId = localStorage.getItem('conversationId')
 
const client = new TakeSalesClient({
  agentId: 'your-agent-uuid',
  wsUrl: 'wss://api-agent.takesales.ai/ws',
  conversationId: savedId ?? undefined,
})
 
client.on('sessionCreated', ({ conversationId }) => {
  localStorage.setItem('conversationId', conversationId)
})
 
client.connect()

Com identificação do visitante (Vanilla JS)

const client = new TakeSalesClient({
  agentId: 'your-agent-uuid',
  wsUrl: 'wss://api-agent.takesales.ai/ws',
  visitor: {
    email: 'visitor@example.com',
    name: 'John Doe',
    phone: '+5511999999999',
  },
})
 
// Custom fields are only accepted after the session exists
client.on('sessionCreated', () => {
  client.sendUpdateVisitor({ contactData: { plan: 'pro' } })
})
 
client.connect()

Client tools

Client tools são funções do seu site que o agente pode chamar. Registre antes de connect(): a lista vai no setup da conexão (máximo de 50 tools, nome com até 64 caracteres).

client.registerTool('get_cart', {
  description: 'Returns the items in the visitor cart',
  parameters: { type: 'object', properties: {} },
  handler: async () => ({ items: cart.items }),
})
 
client.connect()

O SDK executa o handler e devolve o resultado ao agente sozinho. Os args são gerados pelo modelo e podem ser induzidos pelo visitante ou por conteúdo externo: valide tudo no handler e não execute ações sensíveis sem confirmação do usuário.


Troubleshooting

Conexão falhando

  • Verifique se o wsUrl está correto: wss://api-agent.takesales.ai/ws
  • Confirme que o agentId existe e corresponde a um agente ativo no dashboard
  • Verifique se o domínio da sua aplicação está na lista de origins permitidas nas configurações do agente

Nenhuma mensagem recebida

  • Confirme que o agente tem um system prompt configurado
  • Verifique se o agente tem pelo menos uma fonte na base de conhecimento (se aplicável ao seu caso de uso)
  • Verifique o evento error para mensagens de diagnóstico do servidor

Erros de CORS

  • Adicione o domínio exato da sua aplicação (incluindo protocolo e porta) às origins permitidas nas configurações do agente
  • Em desenvolvimento local, adicione http://localhost:3000 (ou a porta que estiver usando)

Nenhuma resposta durante atendimento humano

Quando a conversa é transferida para um atendente, a IA para de responder e as mensagens do atendente não chegam ao SDK 0.1.33. Se o agente tem transferência para humano habilitada, use o widget embutido.

Loop de reconexão

  • Se o evento error trouxe "Invalid setup message", o servidor recusou os dados da conexão (por exemplo, conversationId que não é UUID ou mais de 50 client tools). O 0.1.33 tenta reconectar com os mesmos dados: corrija a config em vez de esperar
  • Verifique se o agente está ativo (não pausado ou arquivado) no dashboard
  • Confirme que o plano do workspace tem créditos disponíveis
  • Se o problema persistir, desabilite a reconexão automática para investigar: reconnect: { enabled: false }