@zihin/agent-client 0.6.0 → 0.7.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 CHANGED
@@ -111,6 +111,92 @@ gera **outra** sessão, e a ordem dos campos entra no hash. Chave repetida nos d
111
111
  `bindings`. Campo do `sessionFrom` que a redação alterou (no `context`) é recusado com
112
112
  `VALIDATION_ERROR` (sai como `config`): mande-o em `bindings`.
113
113
 
114
+ ### Nome do contato e sugestões de resposta (0.6.1)
115
+
116
+ > Backend: zihin-agent-builder `1aa752e6` (+ migration `20261001000002`). Contra um backend
117
+ > anterior os dois recursos só não acontecem: o turno roda normalmente.
118
+
119
+ ```ts
120
+ const r = await zihin.invokeAgent({
121
+ agentId,
122
+ message: 'Quero canetas azuis',
123
+ consumer: { key: 'dev-cotai-1410', name: 'Fernando Produtor' }, // nome aparece nos contatos do console
124
+ capabilities: { blocks: ['quick_replies'] }, // minha tela mostra sugestões
125
+ });
126
+ r.suggestions; // ['Caneta A', 'Caneta B'] — ou undefined quando o agente não sugeriu
127
+ ```
128
+
129
+ - **`consumer: { key, name? }`** é o `consumerKey` com o nome junto. `consumer.key` e `consumerKey`
130
+ são o mesmo campo (mande um; se vierem os dois, têm de ser iguais) e seguem a mesma regra: só
131
+ valem sob API key admin/owner, e o núcleo recusa o turno se o servidor os ignorar. O nome vai como
132
+ `consumer_display_name` (até 255 caracteres) e vale **do presente em diante**: atualiza o
133
+ contato e a conversa em andamento; conversas encerradas guardam o nome da época (o histórico não
134
+ é reescrito). Dado pessoal: mande só o que a operação precisa ver.
135
+ - **Uma `consumer.key` por pessoa.** A chave é a identidade: memória, bloqueio e engajamento são
136
+ dela. Se várias pessoas usam o mesmo canal (ex.: um WhatsApp comercial que troca de vendedor),
137
+ dê a cada uma a sua chave — trocar só o `name` não separa as pessoas.
138
+ - **`capabilities: { blocks: ['quick_replies'] }`** diz que a sua tela renderiza sugestões de
139
+ resposta. Sem declarar, o agente nem recebe a ferramenta — nada é gerado e nada é cobrado. É um
140
+ **pedido**, não uma exigência: qualquer API key pode mandar, e um servidor que não o conheça só o
141
+ reporta em `ignoredFields` (o turno não falha). O dono do agente pode desligar as sugestões
142
+ (`quick_replies_enabled`), e o agente decide quando vale sugerir — nem todo turno traz.
143
+ - **`suggestions`** (em `AgentResult` e no chunk `response`) são os rótulos do bloco `quick_replies`
144
+ de `blocks`, já filtrados: até 4, curtos. Tocar uma sugestão = mandar o texto dela como a próxima
145
+ `message`. Num turno só com sugestões, `content` vem **vazio**.
146
+ - **Descontinuados**: `options.jitMode`, `options.temperature` e `options.maxTokens` não têm efeito no
147
+ `/stream` (temperatura e teto de saída são configuração do agente). Continuam sendo enviados e o
148
+ servidor os lista em `ignoredFields`; o núcleo emite um `ZihinDeprecationWarning` **uma vez por
149
+ processo** (`ZIHIN_DEP001`/`ZIHIN_DEP002`). Saem do SDK na 1.0.
150
+
151
+ ### Idempotência, recusa do provedor e limite de contexto (0.7)
152
+
153
+ > Backend: zihin-agent-builder `server-llm` de outubro/2026 (`client_message_id` em `121e426f`,
154
+ > `REFUSED` em `4cea8d53`, `CONTEXT_LIMIT` em `e670f530`, trava de sessão em `b67141d7`). Verificado
155
+ > em `9ec79cfc` (03/11/2026).
156
+
157
+ ```ts
158
+ const r = await zihin.invokeAgent({
159
+ agentId,
160
+ message: 'Fechar o pedido 42',
161
+ clientMessageId: crypto.randomUUID(), // um UUID novo por mensagem; reenvie o MESMO para retentar
162
+ });
163
+ r.idempotentReplay; // true = o BE repetiu a resposta gravada (o agente não rodou de novo)
164
+ ```
165
+
166
+ - **`clientMessageId`** (UUID) torna o turno idempotente. Com ele, o núcleo passa a **retentar o
167
+ POST** do `/stream` em erro de rede e 5xx antes do stream abrir (sem ele, POST nunca retenta em
168
+ rede — poderia duplicar o turno). Reenviar o mesmo id com a mesma mensagem: turno ainda em voo →
169
+ `SESSION_BUSY` (`blocked`, retentável); turno concluído com `COMPLETED`/`LENGTH` → o BE repete a
170
+ resposta gravada por 24 h, com `idempotentReplay: true` e sem `usage`. Mesmo id com **outra**
171
+ mensagem → `IDEMPOTENCY_CONFLICT` (`config`). **A garantia cobre só o turno concluído**: turno
172
+ cortado no meio do stream, cancelado, com erro ou timeout libera o id e o reenvio roda de novo
173
+ (tools incluídas); por isso o retry automático é só pré-stream, e é best-effort (Redis do BE fora →
174
+ sem dedupe). O escopo é a API key + `consumerKey` (não a sessão) e a comparação é só
175
+ mensagem+anexos: **nunca use um valor fixo** (uma automação agendada receberia a mesma resposta por
176
+ 24 h); reusar o id noutra sessão dá `config` (`IDEMPOTENT_REPLAY_OTHER_SESSION`; comparação de
177
+ sessão case-insensitive, o BE devolve o `session_id` em minúsculas). Essa guarda só existe em
178
+ `invokeAgent` **com `sessionId` explícito**: com `sessionFrom`, sem sessão, ou em `streamAgent`,
179
+ um id reusado devolve em silêncio a resposta (e o `session_id`) da conversa original. O BE ecoa o id
180
+ em minúsculas. `SERVICE_UNAVAILABLE` (replay sem resposta recuperável) **não é retentável**: pode
181
+ durar 24 h e só um `clientMessageId` novo sai dele.
182
+ Contra um servidor que não liste `client_message_id` em `accepted_fields`, o núcleo recusa o turno
183
+ (`config`): a garantia que você pediu não existiria.
184
+ - **`REFUSED`**: o provedor do modelo recusou por política. O BE entrega um texto neutro fixo como
185
+ `response` e marca a execução como erro; o núcleo **lança** `blocked` com `code: 'REFUSED'` (na 0.6
186
+ o texto neutro saía como resposta). **`CONTEXT_LIMIT`**: a conversa reuniu mais contexto do que o
187
+ modelo processa — `blocked`, `code: 'CONTEXT_LIMIT'`; peça menos ou abra outra sessão.
188
+ - **Trava de um turno por sessão** (ligada por padrão no BE desde 18/10/2026): mandar a segunda
189
+ mensagem antes da primeira terminar dá `SESSION_BUSY` como `event: error`. O núcleo o classifica
190
+ `blocked` **retentável** (o turno nem começou) — antes saía como `server` não retentável.
191
+ - **Ids do turno** em `AgentResult`: `executionId` agora vem do `metadata` (chega mesmo quando o
192
+ turno morre antes do `metrics`), mais `clientMessageId`, `userMessageId`, `assistantMessageId`.
193
+ - **`capabilities.blocks`** aceita `'html_report'`: a tela renderiza HTML, e o BE orienta o agente a
194
+ entregar, quando couber, um documento HTML num bloco ```html dentro do `content` (não é bloco novo).
195
+ - **`dispatchAgent` (webhook sync)** lê o `outcome` do corpo: `REFUSED`, `CONTEXT_LIMIT`, `TIMEOUT`,
196
+ `ERROR`… chegam com `success: true` e a frase da plataforma em `agent_response`; o núcleo lança a
197
+ mesma classificação do `/stream`. `DispatchResult.outcome` expõe os que respondem.
198
+ - Novo helper exportado `errorFromOutcome(outcome, ctx)` para quem lê desfechos fora do SSE.
199
+
114
200
  ## Uso — caminho assíncrono (triggers)
115
201
 
116
202
  ```ts
@@ -238,8 +324,8 @@ direto aplica a mesma regra com `errorFromDoneChunk(chunk, { executionId, sessio
238
324
 
239
325
  O mesmo vale para os outros desfechos em que o `content` é falha ou recusa do motor, e não
240
326
  resposta: `ERROR`/`CANCELLED` (pelo servidor) → `kind: 'server'`, `QUOTA_EXCEEDED` → `quota_exceeded`,
241
- `SUSPENDED`/`BUDGET_EXCEEDED`/`SESSION_BUSY`/`CONFLICT` e o `PAUSED` de agente pausado/arquivado →
242
- `blocked` (o `code` é o `outcome`; só `SESSION_BUSY` é retentável). Um `ERROR` precedido de
327
+ `SUSPENDED`/`BUDGET_EXCEEDED`/`SESSION_BUSY`/`CONFLICT`/`REFUSED`/`CONTEXT_LIMIT` e o `PAUSED` de
328
+ agente pausado/arquivado → `blocked` (o `code` é o `outcome`; só `SESSION_BUSY` é retentável). Um `ERROR` precedido de
243
329
  `status.blocked` (gate da plataforma) também sai `blocked`, com o motivo em `code`
244
330
  (`ownership_denied` → `not_found`).
245
331
 
@@ -265,8 +351,9 @@ const agents = await zihin.listAgents(); // GET /api/v2/agents
265
351
 
266
352
  ## Erros
267
353
 
268
- Todos os erros são `ZihinAgentError` com `kind` (`config | auth | not_found | rate_limit | timeout |
269
- aborted | server | network | protocol | unknown`), `status`, `code`, `retryable` e — para depurar em
354
+ Todos os erros são `ZihinAgentError` com `kind` (`config | auth | trial_expired | no_subscription |
355
+ quota_exceeded | not_found | rate_limit | timeout | aborted | server | network | protocol | blocked |
356
+ unknown`), `status`, `code`, `retryable` e — para depurar em
270
357
  produção externa — `endpoint` (`"POST host/path"`), `attempts` e **`requestId`** (handle do BE para o
271
358
  suporte correlacionar). Erros de rede carregam a causa-raiz (`ECONNREFUSED`/`ENOTFOUND`/TLS…) na
272
359
  mensagem. As mensagens redigem **só segredos** (key/Bearer/stack) — `request_id`/`trace_id` são
@@ -308,7 +395,15 @@ correlacionar com o console.
308
395
  da tentativa seguinte — o que fica limitado é o tempo gasto com retries.
309
396
  - **Retry sem duplicar efeito** — GET (idempotente) retenta em rede/429/5xx; POST com efeito
310
397
  (`invokeAgent`/`dispatchAgent`/`createTrigger`) retenta **só em 429/503** e **nunca em erro de
311
- rede** (o request pode ter sido processado). Backoff exponencial 1s→10s com jitter.
398
+ rede** (o request pode ter sido processado). Exceção: `invokeAgent` com `clientMessageId` retenta
399
+ também em rede e 5xx, **só antes dos headers do SSE chegarem**. É seguro porque o BE envia os
400
+ headers antes de reservar o id e antes de qualquer trabalho do turno; se o 1º POST chegou a rodar,
401
+ a chave transforma o reenvio em `SESSION_BUSY` ou replay. Um stream **cortado no meio** sai
402
+ `network` com `retryable: false`: o BE cancela o turno e libera o id, então reenviar (mesmo com a
403
+ chave) roda as tools de novo. Contra um BE antigo que ainda não aceite `client_message_id`, o
404
+ retry pré-headers acontece sem a chave valer (o turno falha depois em `config`); a ordem
405
+ headers-antes-do-trabalho é a mesma, mas a garantia depende do BE. Backoff exponencial 1s→10s
406
+ com jitter.
312
407
  - **Anti-DoS de memória** — limites de stream `maxStreamEventBytes` (1MB) e `maxStreamTotalBytes`
313
408
  (64MB); estourar aborta com `protocol`. Configuráveis no `createClient`.
314
409
  - **`includeRaw`** — por padrão `invokeAgent` não retém os eventos brutos (`raw: []`) para economizar
package/dist/index.d.mts CHANGED
@@ -118,7 +118,17 @@ interface Attachment {
118
118
  type: AttachmentType;
119
119
  }
120
120
  interface InvokeOptions {
121
+ /**
122
+ * @deprecated Sem efeito: o `/stream` usa a temperatura da configuração do agente
123
+ * (`llm_config.temperature`, no console ou via `update_agent`). O servidor ignora o campo e o
124
+ * reporta em `ignoredFields`. Será retirado do SDK na 1.0.
125
+ */
121
126
  temperature?: number;
127
+ /**
128
+ * @deprecated Sem efeito: o teto de saída é a configuração do agente (`llm_config.max_tokens`,
129
+ * no console ou via `update_agent`). O servidor ignora o campo e o reporta em `ignoredFields`.
130
+ * Será retirado do SDK na 1.0.
131
+ */
122
132
  maxTokens?: number;
123
133
  /**
124
134
  * Teto do TURNO desta chamada (ms); sobrepõe o do cliente. Vai ao BE como `options.timeout_ms`
@@ -130,9 +140,49 @@ interface InvokeOptions {
130
140
  * local soma `DEADLINE_GRACE_MS` a este valor para ainda receber o desfecho do servidor.
131
141
  */
132
142
  timeoutMs?: number;
133
- /** Just-in-time tool loading (Sprint 33). */
143
+ /**
144
+ * @deprecated Recurso descontinuado no servidor (ago/2026): sem efeito, reportado em
145
+ * `ignoredFields`. Remova-o. Será retirado do SDK na 1.0.
146
+ */
134
147
  jitMode?: boolean;
135
148
  }
149
+ /**
150
+ * Blocos estruturados que a sua interface sabe renderizar (`capabilities.blocks`). Valor que o BE
151
+ * não conhece sai em `ignoredFields` como `capabilities.blocks.<v>` + `not_supported` (aviso, não erro).
152
+ */
153
+ type ClientBlockType = 'quick_replies' | 'html_report' | (string & {});
154
+ /**
155
+ * O que a SUA tela sabe mostrar. O agente só oferece o que você declarar — sem declarar, nada é
156
+ * gerado e nada é cobrado. Não exige privilégio de API key.
157
+ */
158
+ interface ClientCapabilities {
159
+ /**
160
+ * `quick_replies`: até 4 sugestões curtas de resposta ao fim do turno (o usuário toca e o texto
161
+ * vira a próxima mensagem). Chegam em `AgentResult.suggestions` (e no bloco `quick_replies` de
162
+ * `blocks`). O dono do agente pode desligá-las (`quick_replies_enabled`); o agente decide quando
163
+ * vale sugerir — nem todo turno traz sugestões.
164
+ *
165
+ * `html_report`: a tela renderiza HTML. Não cria bloco novo: o BE orienta o agente a entregar,
166
+ * quando couber, um documento HTML completo num bloco de código ```html dentro do `content`
167
+ * (BE `14d2a43c`, 19/10/2026). Só declare se você realmente renderiza HTML — senão o usuário
168
+ * recebe markup cru.
169
+ */
170
+ blocks?: ClientBlockType[];
171
+ }
172
+ /** A pessoa do outro lado da conversa. Só vale sob API key **admin/owner**. */
173
+ interface ConsumerIdentity {
174
+ /**
175
+ * Identificador estável da pessoa no SEU sistema (memória por pessoa, denylist, gate de
176
+ * engajamento). Para continuar conversas que um webhook já gravava, use o MESMO valor que o
177
+ * webhook usava. Enviado como `consumer_key`.
178
+ */
179
+ key: string;
180
+ /**
181
+ * Nome para exibição no console da Zihin (lista de contatos). Enviado como
182
+ * `consumer_display_name`. Dado pessoal: mande só o que a operação precisa ver.
183
+ */
184
+ name?: string;
185
+ }
136
186
  interface InvokeAgentParams {
137
187
  /** UUID do agente (path :agent_id). */
138
188
  agentId: string;
@@ -142,12 +192,41 @@ interface InvokeAgentParams {
142
192
  attachments?: Attachment[];
143
193
  /** Continuidade de conversa (Supabase agent_sessions). Não combina com `sessionFrom`. */
144
194
  sessionId?: string;
195
+ /**
196
+ * Chave de idempotência do turno (UUID, qualquer versão; BE normaliza para minúsculas).
197
+ * Reenviar a MESMA mensagem com o mesmo id não roda o agente de novo: enquanto o turno original
198
+ * está em voo o BE responde `SESSION_BUSY` (`blocked`, retentável); depois de concluído com
199
+ * `COMPLETED`/`LENGTH` o BE **repete a resposta gravada** (`AgentResult.idempotentReplay: true`,
200
+ * sem `usage`, por 24 h). Mesmo id com OUTRA mensagem → `IDEMPOTENCY_CONFLICT` (`config`).
201
+ * Turno que terminou em erro/timeout/gate libera o id: o reenvio roda de novo.
202
+ *
203
+ * Com ele, o núcleo passa a **retentar o POST** do `/stream` em erro de rede e 5xx antes do
204
+ * stream abrir (sem ele, POST nunca retenta em rede — poderia duplicar o turno).
205
+ *
206
+ * ⚠️ **O que a garantia cobre:** só não repetir um turno **concluído** (`COMPLETED`/`LENGTH`).
207
+ * Turno cortado no meio do stream, cancelado, com erro ou timeout **libera o id** e o reenvio
208
+ * roda de novo — inclusive tools de efeito colateral que já rodaram. É best-effort (Redis do BE
209
+ * fora → sem dedupe, campo ainda aceito). O escopo é API key + `consumerKey` (não a sessão) e a
210
+ * comparação é só mensagem+anexos: **nunca use um valor fixo** — um workflow agendado com a
211
+ * mesma mensagem receberia a mesma resposta por 24 h; reusar o id noutra sessão dá erro
212
+ * `config` (`IDEMPOTENT_REPLAY_OTHER_SESSION`). O BE devolve o id em minúsculas. Vale sob
213
+ * qualquer papel de API key. Enviado como `client_message_id`; contra um BE que não o aceite
214
+ * (`accepted_fields` sem ele) o núcleo recusa o turno (`config`).
215
+ */
216
+ clientMessageId?: string;
145
217
  /**
146
218
  * Identidade da pessoa do outro lado (memória por pessoa, denylist, gate de engajamento). Só vale
147
219
  * sob API key **admin/owner**. Para continuar conversas que um webhook já gravava, use o MESMO
148
220
  * valor que o webhook usava. Enviado como `consumer_key`.
149
221
  */
150
222
  consumerKey?: string;
223
+ /**
224
+ * A pessoa do outro lado: `{ key, name? }`. `consumer.key` é o mesmo que `consumerKey` (use um
225
+ * dos dois; se vierem os dois, têm de ser iguais). Só vale sob API key admin/owner.
226
+ */
227
+ consumer?: ConsumerIdentity;
228
+ /** O que a sua interface sabe renderizar — ex.: `{ blocks: ['quick_replies'] }`. */
229
+ capabilities?: ClientCapabilities;
151
230
  /**
152
231
  * Contexto estruturado que o **modelo lê** — bloco de plataforma junto da mensagem do usuário
153
232
  * (não no system prompt). **Fica gravado** com a mensagem (`declared_context`) e se repete em
@@ -228,10 +307,31 @@ interface AgentResult {
228
307
  type: string;
229
308
  [k: string]: unknown;
230
309
  }>;
310
+ /**
311
+ * Sugestões de resposta do turno (bloco `quick_replies`), já extraídas de `blocks`. Só vêm se
312
+ * você declarou `capabilities: { blocks: ['quick_replies'] }`. O texto de cada uma é o que o
313
+ * usuário "diz" ao tocá-la. Num turno só com sugestões, `content` vem vazio.
314
+ */
315
+ suggestions?: string[];
231
316
  iterations?: number;
232
317
  executionTimeMs?: number;
233
- /** `execution_id` do turno (evento `metrics`) — handle de correlação no console/telemetria. */
318
+ /**
319
+ * `execution_id` do turno — handle de correlação no console/telemetria. Lido do `metadata`
320
+ * (BE desde 17/10/2026) com fallback no `metrics`; ausente num turno barrado por gate.
321
+ */
234
322
  executionId?: string;
323
+ /** `client_message_id` que o BE aplicou ao turno (eco do que você mandou, em **minúsculas**). */
324
+ clientMessageId?: string;
325
+ /**
326
+ * `true` = esta é a resposta GRAVADA de um turno anterior com o mesmo `clientMessageId`
327
+ * (reenvio idempotente). O agente não rodou de novo: sem `usage`/`model`/`iterations`, e
328
+ * `truncated`/`finishReason` não vêm mesmo que o original fosse `LENGTH`.
329
+ */
330
+ idempotentReplay?: boolean;
331
+ /** Id da mensagem do usuário gravada na sessão (quando o BE o deu). */
332
+ userMessageId?: string;
333
+ /** Id da mensagem do agente gravada na sessão (quando o BE o deu). */
334
+ assistantMessageId?: string;
235
335
  /** Campos do pedido que o BE aplicou (`metadata.accepted_fields`). Ausente = backend antigo. */
236
336
  acceptedFields?: string[];
237
337
  /** Campos do pedido que o BE recebeu e ignorou (`metadata.ignored_fields`). */
@@ -246,8 +346,9 @@ interface AgentResult {
246
346
  * Desfecho do turno, lido do `done` (`exitReason` do motor). `undefined` = backend antigo ou
247
347
  * stream cortado antes do `done`. Só chegam aqui os desfechos cujo `content` é resposta
248
348
  * (`COMPLETED`, `LENGTH`), o `PAUSED` (aprovação pendente ou agente pausado) e desfechos novos.
249
- * `TIMEOUT`, `ERROR`, `CANCELLED`, `SUSPENDED` e os de bloqueio viram `ZihinAgentError` — o
250
- * `content` desses turnos é a mensagem de falha/recusa do motor (`errorFromDoneChunk`).
349
+ * `TIMEOUT`, `ERROR`, `CANCELLED`, `SUSPENDED`, `REFUSED`, `CONTEXT_LIMIT` e os de bloqueio
350
+ * viram `ZihinAgentError` — o `content` desses turnos é a mensagem de falha/recusa do motor
351
+ * (`errorFromDoneChunk`).
251
352
  */
252
353
  outcome?: AgentOutcome;
253
354
  /** `true` quando a resposta foi cortada pelo teto de saída do modelo (`done.truncated`). */
@@ -262,7 +363,11 @@ interface AgentResult {
262
363
  * contam como sucesso no BE (`isSuccessfulOutcome`); os demais valores são repassados como vêm.
263
364
  * Aberto (`string & {}`) porque o BE pode acrescentar desfechos (ex.: os de bloqueio).
264
365
  */
265
- type AgentOutcome = 'COMPLETED' | 'LENGTH' | 'TIMEOUT' | 'ERROR' | 'CANCELLED' | 'PAUSED' | 'SUSPENDED' | 'BUDGET_EXCEEDED' | 'QUOTA_EXCEEDED' | 'SESSION_BUSY' | 'CONFLICT' | (string & {});
366
+ type AgentOutcome = 'COMPLETED' | 'LENGTH' | 'TIMEOUT' | 'ERROR' | 'CANCELLED' | 'PAUSED' | 'SUSPENDED' | 'BUDGET_EXCEEDED' | 'QUOTA_EXCEEDED' | 'SESSION_BUSY' | 'CONFLICT'
367
+ /** O provedor do modelo recusou por política (desde 08/10/2026). O `content` é um texto neutro fixo. */
368
+ | 'REFUSED'
369
+ /** A conversa reuniu mais contexto do que o modelo processa (desde 14/10/2026). No `/stream` chega como `event: error`. */
370
+ | 'CONTEXT_LIMIT' | (string & {});
266
371
  /**
267
372
  * Qual relógio encerrou o turno (contrato de tempo §2): `turn` (prazo do turno, inclusive o
268
373
  * `options.timeoutMs` pedido), `first_signal` (o provedor não começou a responder), `idle`
@@ -286,6 +391,11 @@ type AgentStreamChunk =
286
391
  requestId?: string;
287
392
  agentId?: string;
288
393
  sessionId?: string;
394
+ /** `execution_id` do turno (BE desde 17/10/2026). Ausente num turno barrado por gate. */
395
+ executionId?: string;
396
+ clientMessageId?: string;
397
+ /** `true` = o BE vai repetir a resposta gravada (mesmo `clientMessageId`), sem rodar o agente. */
398
+ idempotentReplay?: boolean;
289
399
  acceptedFields?: string[];
290
400
  ignoredFields?: IgnoredField[];
291
401
  data: unknown;
@@ -316,6 +426,12 @@ type AgentStreamChunk =
316
426
  } | {
317
427
  type: 'action_proposed';
318
428
  data: unknown;
429
+ }
430
+ /** Aprovação humana encaminhada ao aprovador (`{ action_id, kind, title, expires_at }`); o turno fica `PAUSED`. */
431
+ | {
432
+ type: 'action_forwarded';
433
+ actionId?: string;
434
+ data: unknown;
319
435
  } | {
320
436
  type: 'token';
321
437
  content: string;
@@ -327,7 +443,9 @@ type AgentStreamChunk =
327
443
  }
328
444
  /**
329
445
  * O BE encerra o stream com `event: error` + `{ error: { type, code, message, request_id,
330
- * timed_out? } }` (`utils/sse.js`) — inclusive quando o TURNO morre no deadline. Antes isto
446
+ * timed_out? } }` (`utils/sse.js`) — inclusive quando o TURNO morre no deadline. O BE NÃO põe
447
+ * `status` nem `details` no fio (ficam no servidor): `SESSION_BUSY`, `IDEMPOTENCY_CONFLICT`,
448
+ * `CONTEXT_LIMIT`, `CONFLICT` e os de quota chegam só com `code`+`message`. Antes isto
331
449
  * caía em `unknown` e o `invokeAgent` ignorava: se já tinha chegado token, devolvia a
332
450
  * deliberação crua como SUCESSO.
333
451
  *
@@ -367,6 +485,11 @@ type AgentStreamChunk =
367
485
  timeoutClock?: TimeoutClock;
368
486
  truncated?: boolean;
369
487
  finishReason?: string;
488
+ executionId?: string;
489
+ clientMessageId?: string;
490
+ idempotentReplay?: boolean;
491
+ userMessageId?: string;
492
+ assistantMessageId?: string;
370
493
  data: unknown;
371
494
  } | {
372
495
  type: 'response';
@@ -377,8 +500,15 @@ type AgentStreamChunk =
377
500
  type: string;
378
501
  [k: string]: unknown;
379
502
  }>;
503
+ /** Sugestões de resposta (bloco `quick_replies`), extraídas de `blocks`. */
504
+ suggestions?: string[];
380
505
  /** Resposta ENLATADA de um gate (handoff humano, denylist, sessão suspensa) — não do agente. */
381
506
  gated?: boolean;
507
+ executionId?: string;
508
+ clientMessageId?: string;
509
+ idempotentReplay?: boolean;
510
+ userMessageId?: string;
511
+ assistantMessageId?: string;
382
512
  data: unknown;
383
513
  } | {
384
514
  type: 'metrics';
@@ -500,7 +630,7 @@ interface TriggerInfo {
500
630
  interface ListTriggersParams {
501
631
  /** Filtra por agente. */
502
632
  agentId?: string;
503
- /** Filtra por tipo (webhook | db_event | email | schedule). */
633
+ /** Filtra por tipo (`webhook` | `email` | `schedule`; `db_event` foi removido do BE em 03/10/2026). */
504
634
  triggerType?: string;
505
635
  /** Apenas habilitados. */
506
636
  enabledOnly?: boolean;
@@ -529,9 +659,19 @@ interface DispatchResult {
529
659
  accepted: boolean;
530
660
  triggerId?: string;
531
661
  agentId?: string;
662
+ /** Id da execução do TRIGGER (`trigger_executions`), não o `execution_id` do turno do agente. */
532
663
  executionId?: string;
533
664
  /** Presente quando o trigger é `sync`: resposta imediata do agente. */
534
665
  agentResponse?: string;
666
+ /**
667
+ * Desfecho do turno no modo `sync` (BE desde 14/10/2026; ausente = backend antigo). Só chegam
668
+ * aqui os desfechos cujo `agentResponse` é resposta (`COMPLETED`, `LENGTH`) e o `PAUSED`; os
669
+ * demais (`REFUSED`, `CONTEXT_LIMIT`, `TIMEOUT`, `ERROR`…) viram `ZihinAgentError` mesmo com
670
+ * `success: true` no corpo — o BE mantém `success` como "entregou", e o texto é a frase da
671
+ * plataforma, não uma resposta. Limite: o sync não traz `status.blocked`, então o `PAUSED` de
672
+ * agente pausado/arquivado chega aqui com o texto de recusa em `agentResponse`.
673
+ */
674
+ outcome?: AgentOutcome;
535
675
  usage?: Usage;
536
676
  /** Iterações do laço de tools no turno (sync). */
537
677
  iterations?: number;
@@ -811,15 +951,32 @@ declare function createClient(config: ZihinClientConfig): ZihinAgentClient;
811
951
  * O regex no nome só vale como FALLBACK para backends que ainda não emitem `timed_out`
812
952
  * (self-hosted/antigos). Some numa major futura, depois do rename no BE.
813
953
  */
814
- declare function classifyStreamErrorKind(code: string, timedOut?: boolean): 'timeout' | 'server' | 'config';
954
+ declare function classifyStreamErrorKind(code: string, timedOut?: boolean): ZihinErrorKind;
815
955
  /**
816
956
  * Converte o chunk `type: 'error'` (evento terminal do BE) num `ZihinAgentError` — a MESMA regra
817
957
  * que o `invokeAgent` aplica, exportada para quem consome `streamAgent` direto (o adapter do
818
958
  * Paperclip, por exemplo) não reimplementar a classificação por conta e divergir dela.
959
+ * `ctx` leva os ids já vistos no `metadata` (o `error` em si não os traz).
819
960
  */
820
961
  declare function errorFromStreamChunk(chunk: Extract<AgentStreamChunk, {
821
962
  type: 'error';
822
- }>): ZihinAgentError;
963
+ }>, ctx?: {
964
+ executionId?: string;
965
+ sessionId?: string;
966
+ }): ZihinAgentError;
967
+ /**
968
+ * Converte um `outcome` cru (fora do `/stream`: `outcome` do webhook sync, `exit_reason` de uma
969
+ * execução) num `ZihinAgentError`, ou `undefined` quando o texto que o acompanha É resposta
970
+ * (`COMPLETED`, `LENGTH`, `PAUSED`, desconhecido/ausente). Mesma tabela do `errorFromDoneChunk`,
971
+ * sem o contexto de `status.blocked` que só o SSE tem.
972
+ */
973
+ declare function errorFromOutcome(outcome: string | undefined, ctx?: {
974
+ executionId?: string;
975
+ sessionId?: string;
976
+ status?: number;
977
+ endpoint?: string;
978
+ attempts?: number;
979
+ }): ZihinAgentError | undefined;
823
980
  /**
824
981
  * Converte o `done` de um turno que NÃO produziu resposta num `ZihinAgentError`; devolve
825
982
  * `undefined` quando o `content` é resposta. Mesma regra do `invokeAgent`, exportada para quem
@@ -965,4 +1122,4 @@ declare function signRequest(payload: string, secretKey: string, timestamp?: num
965
1122
  */
966
1123
  declare const MAX_TASK_PAYLOAD_BYTES: number;
967
1124
 
968
- export { type AgentOutcome, type AgentResult, type AgentStreamChunk, type AgentSummary, type Attachment, type AttachmentType, type CancelTaskParams, type CancelTaskResult, type ContextValue, type CreateTriggerParams, DEADLINE_GRACE_MS, DEFAULT_LIMITS, type DelegateTaskParams, type DeleteTriggerParams, type DispatchAgentParams, type DispatchResult, type ExecutionStatus, type FindOrCreateResult, type GetExecutionParams, type GetTaskParams, type GetTriggerParams, type IgnoredField, type InvokeAgentParams, type InvokeOptions, type ListTasksParams, type ListTriggersParams, MAX_TASK_PAYLOAD_BYTES, OPERATION_TIMEOUT_MS, type QueryExtraction, type ResponseTransformMode, type RetryInfo, type SignedRequestHeaders, type Source, type TaskDossier, type TaskExecution, type TaskInfo, type TaskListPage, type TimeoutClock, type TimeoutOperation, type TriggerInfo, type TriggerMode, type TriggerRef, type UpdateTriggerParams, type Usage, ZihinAgentClient, ZihinAgentError, type ZihinClientConfig, type ZihinErrorKind, classifyStreamErrorKind, createClient, errorFromDoneChunk, errorFromStreamChunk, isCommercialBlock, sanitizeApiError, signRequest, validateApiKeyFormat, validateBaseUrl, validatePromptSize };
1125
+ export { type AgentOutcome, type AgentResult, type AgentStreamChunk, type AgentSummary, type Attachment, type AttachmentType, type CancelTaskParams, type CancelTaskResult, type ClientBlockType, type ClientCapabilities, type ConsumerIdentity, type ContextValue, type CreateTriggerParams, DEADLINE_GRACE_MS, DEFAULT_LIMITS, type DelegateTaskParams, type DeleteTriggerParams, type DispatchAgentParams, type DispatchResult, type ExecutionStatus, type FindOrCreateResult, type GetExecutionParams, type GetTaskParams, type GetTriggerParams, type IgnoredField, type InvokeAgentParams, type InvokeOptions, type ListTasksParams, type ListTriggersParams, MAX_TASK_PAYLOAD_BYTES, OPERATION_TIMEOUT_MS, type QueryExtraction, type ResponseTransformMode, type RetryInfo, type SignedRequestHeaders, type Source, type TaskDossier, type TaskExecution, type TaskInfo, type TaskListPage, type TimeoutClock, type TimeoutOperation, type TriggerInfo, type TriggerMode, type TriggerRef, type UpdateTriggerParams, type Usage, ZihinAgentClient, ZihinAgentError, type ZihinClientConfig, type ZihinErrorKind, classifyStreamErrorKind, createClient, errorFromDoneChunk, errorFromOutcome, errorFromStreamChunk, isCommercialBlock, sanitizeApiError, signRequest, validateApiKeyFormat, validateBaseUrl, validatePromptSize };