@zihin/agent-client 0.1.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +122 -8
- package/dist/index.d.mts +789 -0
- package/dist/index.d.ts +788 -20
- package/dist/index.js +1715 -32
- package/dist/index.mjs +1684 -0
- package/package.json +19 -4
- package/dist/agents.d.ts +0 -21
- package/dist/agents.js +0 -52
- package/dist/agents.js.map +0 -1
- package/dist/client.d.ts +0 -43
- package/dist/client.js +0 -75
- package/dist/client.js.map +0 -1
- package/dist/errors.d.ts +0 -35
- package/dist/errors.js +0 -89
- package/dist/errors.js.map +0 -1
- package/dist/http.d.ts +0 -98
- package/dist/http.js +0 -281
- package/dist/http.js.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/internal.d.ts +0 -13
- package/dist/internal.js +0 -40
- package/dist/internal.js.map +0 -1
- package/dist/invoke.d.ts +0 -18
- package/dist/invoke.js +0 -233
- package/dist/invoke.js.map +0 -1
- package/dist/security.d.ts +0 -82
- package/dist/security.js +0 -251
- package/dist/security.js.map +0 -1
- package/dist/sse.d.ts +0 -27
- package/dist/sse.js +0 -138
- package/dist/sse.js.map +0 -1
- package/dist/triggers.d.ts +0 -43
- package/dist/triggers.js +0 -241
- package/dist/triggers.js.map +0 -1
- package/dist/types.d.ts +0 -268
- package/dist/types.js +0 -9
- package/dist/types.js.map +0 -1
package/README.md
CHANGED
|
@@ -75,14 +75,93 @@ const exec = await zihin.getExecution({ triggerId, executionId });
|
|
|
75
75
|
|
|
76
76
|
> O modo `sync` do trigger (`mode: 'sync'`) responde 200 + JSON na hora (`dispatchResult.agentResponse`).
|
|
77
77
|
|
|
78
|
+
### Payload estruturado (não só texto)
|
|
79
|
+
|
|
80
|
+
O caminho direto (`invokeAgent`) só aceita **texto** — o endpoint `/stream` do BE não tem campo de
|
|
81
|
+
payload estruturado. Para disparar um agente a partir de um JSON rico, use triggers: o payload vai
|
|
82
|
+
inteiro no `dispatchAgent` e o `queryExtraction` define como ele vira a mensagem do agente.
|
|
83
|
+
|
|
84
|
+
```ts
|
|
85
|
+
// template: compõe a mensagem a partir de campos aninhados do payload
|
|
86
|
+
await zihin.findOrCreateTrigger({
|
|
87
|
+
agentId,
|
|
88
|
+
name: 'Pedidos',
|
|
89
|
+
mode: 'sync',
|
|
90
|
+
queryExtraction: { mode: 'template', template: 'Pedido {{payload.order.id}}: {{payload.order.note}}' },
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
// field (default): um campo top-level do payload
|
|
94
|
+
// queryExtraction: { mode: 'field', field: 'texto' }
|
|
95
|
+
// full_body: fallback do BE → payload.message || payload.query || JSON.stringify(payload)
|
|
96
|
+
// queryExtraction: { mode: 'full_body' }
|
|
97
|
+
|
|
98
|
+
await zihin.dispatchAgent({ triggerId, payload: { order: { id: 42, note: 'urgente' }, cliente: {...} } });
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
⚠️ O que chega ao agente é **a string extraída**, não o payload inteiro: com `field`/`template`, os
|
|
102
|
+
campos que você não referenciou ficam de fora. Para levar tudo, use `full_body` — e note que ele
|
|
103
|
+
só serializa o payload completo **se não houver** um campo `message`/`query` no topo (nesse caso o
|
|
104
|
+
BE usa esse campo). Precisa de campos extras como contexto estruturado? Isso é `context_mapping`
|
|
105
|
+
no trigger, ainda não exposto pelo núcleo — configure direto no BE.
|
|
106
|
+
|
|
78
107
|
### Ciclo de vida do trigger
|
|
79
108
|
|
|
80
109
|
```ts
|
|
81
110
|
await zihin.listTriggers({ agentId }); // lista (filtros: agentId/triggerType/enabledOnly)
|
|
82
111
|
await zihin.getTrigger({ triggerId }); // detalhe (inclui webhookUrl)
|
|
112
|
+
await zihin.updateTrigger({ triggerId, enabled: false }); // PATCH parcial
|
|
113
|
+
await zihin.updateTrigger({ triggerId, queryExtraction: { mode: 'full_body' } }); // GET+merge+PATCH
|
|
83
114
|
await zihin.deleteTrigger({ triggerId }); // remove (requer key admin/owner; editor → 403)
|
|
84
115
|
```
|
|
85
116
|
|
|
117
|
+
> `updateTrigger` faz GET + merge + PATCH quando você altera config (o BE substitui
|
|
118
|
+
> `trigger_config` inteiro). Há uma janela entre o GET e o PATCH em que uma escrita concorrente
|
|
119
|
+
> pode ser sobrescrita.
|
|
120
|
+
|
|
121
|
+
## Uso — delegação assíncrona durável (tasks)
|
|
122
|
+
|
|
123
|
+
Para runs que estouram o teto do host (o passo do Zapier morre em ~30s) ou que não podem depender
|
|
124
|
+
da conexão viva. A task é persistida no BE, executada fora do request e o resultado notificado.
|
|
125
|
+
Exige key **owner/admin** para delegar e cancelar; editor só lê.
|
|
126
|
+
|
|
127
|
+
```ts
|
|
128
|
+
const task = await zihin.delegateTask({
|
|
129
|
+
agentId,
|
|
130
|
+
instruction: 'Gere o relatório mensal e envie por e-mail',
|
|
131
|
+
title: 'Relatório de agosto',
|
|
132
|
+
payload: { mes: '2026-08' }, // objeto, ≤ 64KB — validado localmente
|
|
133
|
+
deadlineAt: new Date(Date.now() + 3600e3),
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
// polling: pare quando `terminal` for true — qualquer status desconhecido conta como terminal
|
|
137
|
+
let dossie;
|
|
138
|
+
do {
|
|
139
|
+
await new Promise((r) => setTimeout(r, 2000));
|
|
140
|
+
dossie = await zihin.getTask({ taskId: task.id });
|
|
141
|
+
} while (!dossie.task.terminal);
|
|
142
|
+
|
|
143
|
+
console.log(dossie.task.status, dossie.executions.at(-1)?.totalCostUsd);
|
|
144
|
+
await zihin.cancelTask({ taskId: task.id, reason: 'mudou o plano' }); // 409 se já encerrou
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
`listTasks({ live: true })` devolve o board (`items` + `total`, este vindo de `meta`).
|
|
148
|
+
|
|
149
|
+
## O texto que volta — e o que NÃO é resposta
|
|
150
|
+
|
|
151
|
+
`AgentResult.contentSource` diz de onde veio o `content`:
|
|
152
|
+
|
|
153
|
+
- **`'response'`** — o evento final do BE, que no `/stream` é sempre o modo `final` (só as mensagens
|
|
154
|
+
sem `tool_calls`). **É o texto seguro para publicar.**
|
|
155
|
+
- **`'tokens'`** — o stream morreu antes do evento final e sobrou só a concatenação crua dos deltas,
|
|
156
|
+
**com a deliberação interna do modelo**. Foi exatamente isso que vazou para um lead real via
|
|
157
|
+
WhatsApp e que o BE parou de entregar. Trate como parcial de diagnóstico; não mande para fora
|
|
158
|
+
sem revisar.
|
|
159
|
+
|
|
160
|
+
Um `content` vazio com `contentSource: 'response'` é decisão do BE, não lacuna — o núcleo **não**
|
|
161
|
+
preenche com tokens. E quando o BE fecha o stream com `event: error` (turno morto no deadline, por
|
|
162
|
+
exemplo), o núcleo **lança** (`kind: 'timeout'` para `TURN_TIMEOUT`, com `code` e `requestId`) em vez
|
|
163
|
+
de devolver o parcial.
|
|
164
|
+
|
|
86
165
|
## Dropdown de agentes
|
|
87
166
|
|
|
88
167
|
```ts
|
|
@@ -93,8 +172,8 @@ const agents = await zihin.listAgents(); // GET /api/v2/agents
|
|
|
93
172
|
## Permissões da API key
|
|
94
173
|
|
|
95
174
|
- `invokeAgent` / `streamAgent` — qualquer role.
|
|
96
|
-
- `createTrigger` / `getExecution` — exigem key **admin** ou **editor** (default é
|
|
97
|
-
`member` recebem `ZihinAgentError` com `kind: 'auth'`.
|
|
175
|
+
- `createTrigger` / `updateTrigger` / `getExecution` — exigem key **admin** ou **editor** (default é
|
|
176
|
+
admin). Keys `member` recebem `ZihinAgentError` com `kind: 'auth'`.
|
|
98
177
|
|
|
99
178
|
## Erros
|
|
100
179
|
|
|
@@ -105,10 +184,33 @@ suporte correlacionar). Erros de rede carregam a causa-raiz (`ECONNREFUSED`/`ENO
|
|
|
105
184
|
mensagem. As mensagens redigem **só segredos** (key/Bearer/stack) — `request_id`/`trace_id` são
|
|
106
185
|
preservados. Cancele com `AbortSignal` via `params.signal`.
|
|
107
186
|
|
|
187
|
+
### Bloqueio comercial não é credencial errada
|
|
188
|
+
|
|
189
|
+
Trial vencido, assinatura inativa e quota esgotada chegam do BE como 403/429 e, mapeados só pelo
|
|
190
|
+
status, virariam "troque a key" ou "tente de novo". Têm `kind` próprio e **nunca são retentados**:
|
|
191
|
+
|
|
192
|
+
| `kind` | HTTP | O que fazer |
|
|
193
|
+
|---|---|---|
|
|
194
|
+
| `trial_expired` | 403 | Fazer upgrade do plano — a key está certa |
|
|
195
|
+
| `no_subscription` | 429 | Ativar uma assinatura — a key está certa |
|
|
196
|
+
| `quota_exceeded` | 429 | Esperar o ciclo ou subir o plano — retentar não ajuda |
|
|
197
|
+
|
|
198
|
+
`isCommercialBlock(body)` é exportado para quem inspeciona respostas por conta própria. O 403 comum
|
|
199
|
+
(`kind: 'auth'`) significa que o **papel** da key não permite a ação — e o papel exigido varia por
|
|
200
|
+
rota (editor cria trigger, mas não delega task), então a mensagem não enumera papéis.
|
|
201
|
+
|
|
202
|
+
`ZihinAgentError.executionId` vem preenchido quando o BE o entrega (sucesso e erro) — é o handle para
|
|
203
|
+
correlacionar com o console.
|
|
204
|
+
|
|
108
205
|
## Robustez (timeout / retry / limites)
|
|
109
206
|
|
|
110
|
-
- **Timeout cobre tudo** — headers e o corpo/stream inteiro.
|
|
111
|
-
|
|
207
|
+
- **Timeout cobre tudo** — headers e o corpo/stream inteiro. Os defaults são **por operação**
|
|
208
|
+
(`OPERATION_TIMEOUT_MS`): stream 200s, dispatch de trigger 260s, GETs curtos 35s. Cada um fica
|
|
209
|
+
ACIMA do orçamento do BE para aquela rota (SSE 180s, turno async 240s, timeout global 30s), para
|
|
210
|
+
que o desfecho do servidor chegue como erro estruturado em vez de abort cego do cliente.
|
|
211
|
+
`timeoutMs` na config sobrepõe todos; `options.timeoutMs` por chamada vai ao BE como
|
|
212
|
+
`timeout_ms` e o deadline local fica um pouco acima dele. Retentativas NÃO encolhem o deadline
|
|
213
|
+
da tentativa seguinte — o que fica limitado é o tempo gasto com retries.
|
|
112
214
|
- **Retry sem duplicar efeito** — GET (idempotente) retenta em rede/429/5xx; POST com efeito
|
|
113
215
|
(`invokeAgent`/`dispatchAgent`/`createTrigger`) retenta **só em 429/503** e **nunca em erro de
|
|
114
216
|
rede** (o request pode ter sido processado). Backoff exponencial 1s→10s com jitter.
|
|
@@ -123,11 +225,17 @@ preservados. Cancele com `AbortSignal` via `params.signal`.
|
|
|
123
225
|
### Observabilidade e portabilidade
|
|
124
226
|
|
|
125
227
|
- **Hooks** (opt-in, sem `console`): `onRetry(info)` (a cada retentativa, com `endpoint`/`attempt`/
|
|
126
|
-
`delayMs`/`reason`/`status`) e `onError(err)
|
|
228
|
+
`delayMs`/`reason`/`status`) e `onError(err)` — chamado quando uma operação falha em definitivo
|
|
229
|
+
(inclusive `aborted`; filtre por `err.kind`). Exceção lançada dentro do hook é engolida e nunca
|
|
230
|
+
mascara o erro original.
|
|
127
231
|
- **`fetch` injetável** — `fetch?` na config; por padrão usa `globalThis.fetch` (Node ≥18). Um
|
|
128
232
|
preflight dá erro `config` claro se não houver fetch.
|
|
129
233
|
- **Portável** — sem `Buffer`; `node:crypto` só é carregado se você usar `signRequest`. Roda em Node;
|
|
130
|
-
para edge/serverless, injete `fetch` se necessário.
|
|
234
|
+
para edge/serverless, injete `fetch` se necessário. O pacote publica **CJS e ESM** (`exports` map):
|
|
235
|
+
`require()` e `import` funcionam. Duas ressalvas: no build ESM, `signRequest` precisa de Node
|
|
236
|
+
>=20.16 (`process.getBuiltinModule`) e sem isso lança `ZihinAgentError` com `kind: 'config'`; e
|
|
237
|
+
carregar **as duas formas no mesmo processo** cria duas classes `ZihinAgentError` distintas, o que
|
|
238
|
+
faz `instanceof` falhar — padronize `require` **ou** `import` para o núcleo na sua árvore de deps.
|
|
131
239
|
|
|
132
240
|
```ts
|
|
133
241
|
const zihin = createClient({
|
|
@@ -140,8 +248,14 @@ const zihin = createClient({
|
|
|
140
248
|
});
|
|
141
249
|
```
|
|
142
250
|
|
|
143
|
-
> SSRF: a validação de base/callback URL é por nome de host
|
|
144
|
-
>
|
|
251
|
+
> SSRF: a validação de base/callback/attachment URL é por **nome de host** — cobre IPv4 privado
|
|
252
|
+
> (inclusive octal/hex, normalizados pela URL), IPv6 privado **por faixa de bits** (`fc00::/7`,
|
|
253
|
+
> `fe80::/10`, `fec0::/10`, loopback e IPv4 embutido `::ffff:`/`::`), FQDN com ponto final e os
|
|
254
|
+
> hosts de metadata de nuvem.
|
|
255
|
+
>
|
|
256
|
+
> **Limite importante:** é validação por nome, não por IP resolvido. Com `allowCustomBaseUrl: true`,
|
|
257
|
+
> um host que **resolva** para IP privado (DNS rebinding) passa. Isto não substitui bloqueio de
|
|
258
|
+
> egresso na borda de rede — mantenha o whitelist (default) em ambiente sensível.
|
|
145
259
|
|
|
146
260
|
## Segurança
|
|
147
261
|
|