@zihin/agent-client
v0.1.0
Published
Núcleo de invocação de agentes Zihin.ai — padrão único para n8n, Paperclip e integrações futuras. Stream direto (SSE bufferizado) + triggers assíncronos com callback.
Maintainers
Readme
@zihin/agent-client
Núcleo de invocação de agente Zihin.ai — o padrão único que n8n, Paperclip e integrações futuras usam para acionar agentes hospedados na Zihin. Zero dependência de plataforma.
Contexto e decisões:
docs/issues/PLANO-padrao-agent-client.md(issue #16). Contrato verificado contra o BE (zihin-agent-builder/server-llm) em 2026-06-28.
A ideia
Toda entrada externa na Zihin atravessa um agente (RBAC, quota, HITL, auditoria, memória) — não a call crua de LLM. Este pacote esconde os detalhes de transporte (SSE, triggers) atrás de uma API simples, e oferece dois estilos de invocação:
| Estilo | Método | Quando usar |
|---|---|---|
| Direto / imediato | invokeAgent / streamAgent | espera a resposta (n8n, request/resposta). Zero setup. |
| Assíncrono / desacoplado | createTrigger + dispatchAgent (+ getExecution) | dispara e recebe via callback (serverless, execução longa). |
Instalação
npm install @zihin/agent-clientRequer Node >= 18 (usa fetch/ReadableStream globais).
Uso — caminho direto
import { createClient } from '@zihin/agent-client';
const zihin = createClient({ apiKey: process.env.ZIHIN_API_KEY! }); // zhn_live_*
// Imediato — buffeia o SSE e consolida response + metrics
const res = await zihin.invokeAgent({
agentId: 'uuid-do-agente',
message: 'Qual a previsão de vendas?',
sessionId: previousSessionId, // opcional, continua a conversa
});
console.log(res.content); // texto final
console.log(res.usage); // { inputTokens, outputTokens, totalTokens, costUsd }
console.log(res.sessionId); // guarde para a próxima mensagem
console.log(res.model); // modelo efetivo
// Ao vivo — deltas conforme o agente pensa/usa tools/responde
for await (const chunk of zihin.streamAgent({ agentId, message: 'Olá' })) {
if (chunk.type === 'token') process.stdout.write(chunk.content);
if (chunk.type === 'tool') console.log('tool:', chunk.tool);
}Uso — caminho assíncrono (triggers)
// 1x: provisiona (ou reaproveita) um webhook ligado ao agente.
// PREFIRA findOrCreateTrigger — o BE não tem unicidade de nome, então createTrigger
// repetido cria duplicatas. findOrCreate reusa por (agente + nome).
const { triggerId, webhookUrl, created } = await zihin.findOrCreateTrigger({
agentId,
name: 'Integração X',
mode: 'async',
callbackUrl: 'https://minha-plataforma.com/webhook/zihin',
});
// N vezes: dispara (responde 202 na hora; resultado chega no callback)
const { accepted, executionId } = await zihin.dispatchAgent({
triggerId,
payload: { message: 'Processa este pedido' },
});
// opcional: polling do resultado em vez de callback
const exec = await zihin.getExecution({ triggerId, executionId });O modo
syncdo trigger (mode: 'sync') responde 200 + JSON na hora (dispatchResult.agentResponse).
Ciclo de vida do trigger
await zihin.listTriggers({ agentId }); // lista (filtros: agentId/triggerType/enabledOnly)
await zihin.getTrigger({ triggerId }); // detalhe (inclui webhookUrl)
await zihin.deleteTrigger({ triggerId }); // remove (requer key admin/owner; editor → 403)Dropdown de agentes
const agents = await zihin.listAgents(); // GET /api/v2/agents
// [{ id, name, commercialName, status, toolsCount, ... }]Permissões da API key
invokeAgent/streamAgent— qualquer role.createTrigger/getExecution— exigem key admin ou editor (default é admin). KeysmemberrecebemZihinAgentErrorcomkind: 'auth'.
Erros
Todos os erros são ZihinAgentError com kind (config | auth | not_found | rate_limit | timeout |
aborted | server | network | protocol | unknown), status, code, retryable e — para depurar em
produção externa — endpoint ("POST host/path"), attempts e requestId (handle do BE para o
suporte correlacionar). Erros de rede carregam a causa-raiz (ECONNREFUSED/ENOTFOUND/TLS…) na
mensagem. As mensagens redigem só segredos (key/Bearer/stack) — request_id/trace_id são
preservados. Cancele com AbortSignal via params.signal.
Robustez (timeout / retry / limites)
- Timeout cobre tudo — headers e o corpo/stream inteiro.
timeoutMs(default 120s, igual ao cap do BE) é o tempo máximo total da chamada;AbortSignaldo chamador funciona durante todo o stream. - Retry sem duplicar efeito — GET (idempotente) retenta em rede/429/5xx; POST com efeito
(
invokeAgent/dispatchAgent/createTrigger) retenta só em 429/503 e nunca em erro de rede (o request pode ter sido processado). Backoff exponencial 1s→10s com jitter. - Anti-DoS de memória — limites de stream
maxStreamEventBytes(1MB) emaxStreamTotalBytes(64MB); estourar aborta comprotocol. Configuráveis nocreateClient. includeRaw— por padrãoinvokeAgentnão retém os eventos brutos (raw: []) para economizar memória; passeincludeRaw: truepara recebê-los.streamAgentsempre entrega os chunks ao vivo.- Segurança de transporte —
redirect: 'error'(um 3xx para outro host vazaria a key) ecache: 'no-store'(evita polling stale em Next.js/edge) em toda requisição.Retry-Afterdo servidor é respeitado em 429/503.
Observabilidade e portabilidade
- Hooks (opt-in, sem
console):onRetry(info)(a cada retentativa, comendpoint/attempt/delayMs/reason/status) eonError(err). fetchinjetável —fetch?na config; por padrão usaglobalThis.fetch(Node ≥18). Um preflight dá erroconfigclaro se não houver fetch.- Portável — sem
Buffer;node:cryptosó é carregado se você usarsignRequest. Roda em Node; para edge/serverless, injetefetchse necessário.
const zihin = createClient({
apiKey,
timeoutMs: 60_000,
maxRetries: 2,
maxStreamEventBytes: 2 * 1024 * 1024,
onRetry: (info) => myLogger.warn('zihin retry', info),
// fetch: customFetch, // ambientes sem fetch global
});SSRF: a validação de base/callback URL é por nome de host. Com
allowCustomBaseUrl: trueum host que resolva para IP privado (DNS rebinding) passa — mantenha o whitelist em ambiente sensível.
Segurança
Reusa as primitivas de security.ts: validação SSRF da base URL (whitelist Zihin por padrão; libere
com allowCustomBaseUrl), limites de payload, sanitização de erro, validação de formato de key e HMAC
opcional.
Dev
npm run build && npm test # smoke test offline (mocka fetch), sem rede