@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.
@@ -0,0 +1,789 @@
1
+ /**
2
+ * Erros do @zihin/agent-client — sanitizados (sem vazar key/Bearer/stack), mas COMPLETOS:
3
+ * carregam endpoint, nº de tentativas e request_id do BE para depuração em produção externa.
4
+ *
5
+ * @module errors
6
+ */
7
+ type ZihinErrorKind = 'config' | 'auth' | 'trial_expired' | 'no_subscription' | 'quota_exceeded' | 'not_found' | 'rate_limit' | 'timeout' | 'aborted' | 'server' | 'network' | 'protocol' | 'unknown';
8
+ /** Contexto opcional anexado a um erro para correlação/depuração. */
9
+ interface ErrorMeta {
10
+ /** Ex.: "POST llm.zihin.ai/api/v2/agents/abc/stream". */
11
+ endpoint?: string;
12
+ /** Número de tentativas feitas antes de falhar. */
13
+ attempts?: number;
14
+ }
15
+ interface ZihinErrorOptions extends ErrorMeta {
16
+ status?: number;
17
+ code?: string;
18
+ retryable?: boolean;
19
+ /** request_id do BE (NÃO é segredo) — handle para o suporte correlacionar nos logs. */
20
+ requestId?: string;
21
+ /**
22
+ * execution_id do turno. Desde agosto/2026 o BE o entrega no sucesso E no erro (§1.2 do
23
+ * handoff) — é o handle que liga um erro daqui à execução no console/telemetria.
24
+ */
25
+ executionId?: string;
26
+ }
27
+ declare class ZihinAgentError extends Error {
28
+ readonly kind: ZihinErrorKind;
29
+ readonly status?: number;
30
+ readonly code?: string;
31
+ readonly retryable: boolean;
32
+ readonly requestId?: string;
33
+ readonly attempts?: number;
34
+ readonly endpoint?: string;
35
+ readonly executionId?: string;
36
+ constructor(kind: ZihinErrorKind, message: string, opts?: ZihinErrorOptions);
37
+ }
38
+ /**
39
+ * true quando o corpo é um bloqueio comercial. Consultado pelo transporte ANTES de retentar:
40
+ * o 429 de quota é indistinguível de rate limit pelo status, e só o corpo separa os dois.
41
+ */
42
+ declare function isCommercialBlock(body: unknown): boolean;
43
+
44
+ /**
45
+ * Tipos do contrato externo Zihin — fonte de verdade verificada contra o BE
46
+ * (zihin-agent-builder/server-llm, 2026-06-28).
47
+ *
48
+ * @module types
49
+ */
50
+
51
+ interface ZihinClientConfig {
52
+ /** API key `zhn_live_*` (tenant embutido). Para triggers/telemetria use role admin/editor. */
53
+ apiKey: string;
54
+ /** Base URL. Default: https://llm.zihin.ai */
55
+ baseUrl?: string;
56
+ /**
57
+ * Timeout por request (ms) — override explícito que vale para TODAS as operações. Deixe vazio
58
+ * para usar o default de cada uma (`OPERATION_TIMEOUT_MS`): stream 200s, dispatch de trigger
59
+ * 260s, GETs curtos 35s. Cada default fica acima do orçamento do BE para aquela rota, para que
60
+ * o desfecho do servidor chegue como erro estruturado em vez de abort do cliente — um valor
61
+ * único menor que isso reintroduz exatamente esse problema.
62
+ */
63
+ timeoutMs?: number;
64
+ /** Máximo de retentativas em 5xx/429/erro de rede. Default: 2. */
65
+ maxRetries?: number;
66
+ /** Permite base URL fora da whitelist Zihin (default false → SSRF guard). */
67
+ allowCustomBaseUrl?: boolean;
68
+ /** Permite http:// (apenas dev/local). Default false. */
69
+ allowHttp?: boolean;
70
+ /** Teto de bytes por evento/linha SSE (anti-DoS de memória). Default: 1MB. */
71
+ maxStreamEventBytes?: number;
72
+ /** Teto de bytes do stream inteiro (anti-DoS de memória). Default: 64MB. */
73
+ maxStreamTotalBytes?: number;
74
+ /**
75
+ * Implementação de `fetch` a usar. Default: `globalThis.fetch`. Injete em runtimes onde o
76
+ * fetch não é global (Node <18 com polyfill, testes, proxies).
77
+ */
78
+ fetch?: typeof fetch;
79
+ /**
80
+ * Observabilidade (opt-in, sem console): chamado antes de cada retentativa.
81
+ * Para logar backoff/timeouts/falhas transitórias em produção externa.
82
+ */
83
+ onRetry?: (info: RetryInfo) => void;
84
+ /**
85
+ * Observabilidade: chamado quando uma operação do cliente falha em definitivo (após retries),
86
+ * inclusive `aborted` — filtre por `error.kind` se quiser ignorar cancelamentos. Uma exceção
87
+ * lançada dentro do callback é engolida (não mascara o erro original). Não cobre erros de
88
+ * configuração lançados pelo construtor (o cliente ainda não existe).
89
+ */
90
+ onError?: (error: ZihinAgentError) => void;
91
+ }
92
+ interface RetryInfo {
93
+ /** "POST llm.zihin.ai/api/v2/agents/abc/stream". */
94
+ endpoint: string;
95
+ /** Número da tentativa que acabou de falhar (1 = primeira). */
96
+ attempt: number;
97
+ /** Atraso até a próxima tentativa, em ms. */
98
+ delayMs: number;
99
+ /** Motivo do retry. */
100
+ reason: 'status' | 'network';
101
+ /** Status HTTP, quando `reason === 'status'`. */
102
+ status?: number;
103
+ }
104
+ type AttachmentType = 'image' | 'audio' | 'document';
105
+ interface Attachment {
106
+ url: string;
107
+ type: AttachmentType;
108
+ }
109
+ interface InvokeOptions {
110
+ temperature?: number;
111
+ maxTokens?: number;
112
+ /** Timeout específico desta chamada (ms); sobrepõe o do cliente. */
113
+ timeoutMs?: number;
114
+ /** Just-in-time tool loading (Sprint 33). */
115
+ jitMode?: boolean;
116
+ }
117
+ interface InvokeAgentParams {
118
+ /** UUID do agente (path :agent_id). */
119
+ agentId: string;
120
+ /** Mensagem ao agente. `message` OU `attachments` é obrigatório. */
121
+ message?: string;
122
+ /** Anexos multimodais. `message` OU `attachments` é obrigatório. */
123
+ attachments?: Attachment[];
124
+ /** Continuidade de conversa (Supabase agent_sessions). */
125
+ sessionId?: string;
126
+ options?: InvokeOptions;
127
+ metadata?: {
128
+ traceId?: string;
129
+ correlationId?: string;
130
+ [k: string]: unknown;
131
+ };
132
+ /** Cancelamento externo. */
133
+ signal?: AbortSignal;
134
+ /**
135
+ * Retém todos os eventos SSE brutos em `AgentResult.raw`. Default: false (economiza memória
136
+ * em runs longos). `streamAgent` sempre entrega os chunks ao vivo, independente disto.
137
+ */
138
+ includeRaw?: boolean;
139
+ }
140
+ interface Usage {
141
+ inputTokens?: number;
142
+ outputTokens?: number;
143
+ totalTokens?: number;
144
+ costUsd?: number;
145
+ }
146
+ interface Source {
147
+ title?: string;
148
+ url?: string;
149
+ [k: string]: unknown;
150
+ }
151
+ /**
152
+ * Resultado final de `invokeAgent`: consolida o evento SSE `response`
153
+ * (content/sources/session_id/blocks) com o evento `metrics` (usage/model).
154
+ */
155
+ interface AgentResult {
156
+ content: string;
157
+ /**
158
+ * De onde veio o `content`:
159
+ * - `response`: o evento SSE `response` — o texto que o BE decidiu entregar. No `/stream` isso é
160
+ * sempre o modo `final` (só as mensagens sem `tool_calls`). É o texto seguro para publicar.
161
+ * - `tokens`: o stream terminou SEM o evento `response` e sobrou apenas a concatenação crua dos
162
+ * deltas de todas as iterações — **deliberação interna incluída**. Foi exatamente esse texto
163
+ * que vazou para um lead real e que o BE parou de entregar. Trate como parcial de diagnóstico:
164
+ * não publique em canal externo sem revisar.
165
+ */
166
+ contentSource: 'response' | 'tokens';
167
+ sessionId?: string;
168
+ model?: string;
169
+ usage?: Usage;
170
+ sources?: Source[];
171
+ /** Cards/blocos estruturados (SPEC B). */
172
+ blocks?: Array<{
173
+ type: string;
174
+ [k: string]: unknown;
175
+ }>;
176
+ iterations?: number;
177
+ executionTimeMs?: number;
178
+ /** Eventos SSE brutos coletados (para depuração / casos avançados). */
179
+ raw: AgentStreamChunk[];
180
+ }
181
+ /**
182
+ * Chunk normalizado emitido por `streamAgent`. Mapeia os tipos de evento SSE do BE:
183
+ * metadata, status, attachments, phase, thinking, tool, action_proposed, token,
184
+ * warning, response, metrics.
185
+ */
186
+ type AgentStreamChunk = {
187
+ type: 'metadata';
188
+ requestId?: string;
189
+ agentId?: string;
190
+ sessionId?: string;
191
+ data: unknown;
192
+ } | {
193
+ type: 'status';
194
+ phase?: string;
195
+ data: unknown;
196
+ } | {
197
+ type: 'attachments';
198
+ data: unknown;
199
+ } | {
200
+ type: 'phase';
201
+ phase?: string;
202
+ data: unknown;
203
+ } | {
204
+ type: 'thinking';
205
+ thought?: string;
206
+ data: unknown;
207
+ } | {
208
+ type: 'tool';
209
+ tool?: string;
210
+ callId?: string;
211
+ success?: boolean;
212
+ data: unknown;
213
+ } | {
214
+ type: 'action_proposed';
215
+ data: unknown;
216
+ } | {
217
+ type: 'token';
218
+ content: string;
219
+ data: unknown;
220
+ } | {
221
+ type: 'warning';
222
+ message?: string;
223
+ data: unknown;
224
+ }
225
+ /**
226
+ * O BE encerra o stream com `event: error` + `{ error: { type, code, message, request_id } }`
227
+ * (`utils/sse.js:97`) — inclusive quando o TURNO morre no deadline. Antes isto caía em
228
+ * `unknown` e o `invokeAgent` ignorava: se já tinha chegado token, devolvia a deliberação
229
+ * crua como SUCESSO.
230
+ */
231
+ | {
232
+ type: 'error';
233
+ code?: string;
234
+ message?: string;
235
+ requestId?: string;
236
+ data: unknown;
237
+ }
238
+ /**
239
+ * Emitido no meio do turno SÓ quando o BE cunhou uma sessão diferente da pedida
240
+ * (`agents-v2.js:931`). É o id em que a conversa realmente vai continuar — vence o `session_id`
241
+ * do `metadata` e o que o chamador mandou. Visto em produção no E2E de 01/09.
242
+ */
243
+ | {
244
+ type: 'session';
245
+ sessionId?: string;
246
+ data: unknown;
247
+ }
248
+ /** Fim LIMPO do stream (`utils/sse.js:92`). Sem ele, o stream foi cortado no meio. */
249
+ | {
250
+ type: 'done';
251
+ data: unknown;
252
+ } | {
253
+ type: 'response';
254
+ content: string;
255
+ sessionId?: string;
256
+ sources?: Source[];
257
+ blocks?: Array<{
258
+ type: string;
259
+ [k: string]: unknown;
260
+ }>;
261
+ data: unknown;
262
+ } | {
263
+ type: 'metrics';
264
+ usage?: Usage;
265
+ model?: string;
266
+ iterations?: number;
267
+ executionTimeMs?: number;
268
+ data: unknown;
269
+ } | {
270
+ type: 'unknown';
271
+ event: string;
272
+ data: unknown;
273
+ };
274
+ type TriggerMode = 'sync' | 'async';
275
+ /**
276
+ * Como o BE monta o texto entregue pelo trigger (`#343/#345`):
277
+ * - `final` (default): só as mensagens do agente SEM `tool_calls`. O texto que acompanha uma
278
+ * chamada de tool é deliberação interna — vazou para um lead real via WhatsApp.
279
+ * - `concat` (legado): concatena todas as iterações. Só peça se você realmente depende da
280
+ * narrativa completa e o destino é interno.
281
+ *
282
+ * ⚠️ O BE lê isto de `trigger_config.execution.callback.response_transform.mode`, e o validador dele
283
+ * exige `callback.url` sempre que o objeto `callback` existe. Consequência verificada em 2026-08-31:
284
+ * **um trigger `sync` não consegue declarar o modo sem uma callbackUrl válida**, mesmo que ela nunca
285
+ * seja chamada. `createTrigger` falha alto nesse caso em vez de mandar um config que o BE rejeita.
286
+ */
287
+ type ResponseTransformMode = 'final' | 'concat';
288
+ /**
289
+ * Como o BE extrai a mensagem do agente a partir do payload de disparo (webhook):
290
+ * - `field`: um campo top-level do payload (`payload[field]`).
291
+ * - `template`: compõe a mensagem com placeholders `{{payload.caminho.aninhado}}` —
292
+ * é o modo para payloads estruturados ricos.
293
+ * - `full_body`: fallback do BE — usa `payload.message || payload.query ||
294
+ * JSON.stringify(payload)` (o payload inteiro serializado vira a mensagem).
295
+ * (Verificado no BE em 2026-08-03: `jmespath` citado em docs antigas NÃO é implementado.)
296
+ */
297
+ type QueryExtraction = {
298
+ mode: 'field';
299
+ field: string;
300
+ } | {
301
+ mode: 'template';
302
+ template: string;
303
+ } | {
304
+ mode: 'full_body';
305
+ };
306
+ interface CreateTriggerParams {
307
+ agentId: string;
308
+ /** Nome legível do trigger. */
309
+ name: string;
310
+ /** `async` → 202 + callback; `sync` → 200 + JSON na hora. Default: async. */
311
+ mode?: TriggerMode;
312
+ /** URL que o BE chama com o resultado (obrigatório no modo async). */
313
+ callbackUrl?: string;
314
+ /** Método HTTP do callback. Default: POST. */
315
+ callbackMethod?: 'POST' | 'PUT';
316
+ /** Campo do payload de disparo de onde sai a mensagem do agente. Default: "message". */
317
+ queryField?: string;
318
+ /**
319
+ * Extração da mensagem a partir do payload (field/template/full_body). Tem precedência
320
+ * sobre `queryField` (que equivale a `{ mode: 'field', field: queryField }`).
321
+ */
322
+ queryExtraction?: QueryExtraction;
323
+ /** Auth de disparo aceita. Default: api_key. */
324
+ dispatchAuth?: 'api_key' | 'signature' | 'none';
325
+ /**
326
+ * Modo de extração da resposta do agente. Default do BE: `final`. Exige `callbackUrl` — ver
327
+ * {@link ResponseTransformMode}.
328
+ */
329
+ responseTransform?: ResponseTransformMode;
330
+ signal?: AbortSignal;
331
+ }
332
+ /**
333
+ * Atualização parcial de um trigger (PATCH). Campos de config (queryExtraction/callback/mode)
334
+ * exigem um GET prévio + merge, porque o PATCH do BE SUBSTITUI `trigger_config` por inteiro.
335
+ */
336
+ interface UpdateTriggerParams {
337
+ triggerId: string;
338
+ name?: string;
339
+ description?: string;
340
+ enabled?: boolean;
341
+ /** Troca a extração de query preservando o restante do trigger_config. */
342
+ queryExtraction?: QueryExtraction;
343
+ /** Troca a URL do callback (modo async). */
344
+ callbackUrl?: string;
345
+ /** Troca o método do callback. */
346
+ callbackMethod?: 'POST' | 'PUT';
347
+ /** Troca o modo de execução (sync/async). */
348
+ mode?: TriggerMode;
349
+ /**
350
+ * Troca o modo de extração da resposta, preservando `strip_markdown`/`max_length` já
351
+ * configurados. Exige que o trigger já tenha um `callback` (ou que `callbackUrl` venha junto).
352
+ */
353
+ responseTransform?: ResponseTransformMode;
354
+ signal?: AbortSignal;
355
+ }
356
+ interface TriggerRef {
357
+ triggerId: string;
358
+ /** URL completa de disparo: POST {webhookUrl}. */
359
+ webhookUrl: string;
360
+ }
361
+ /** Resultado de findOrCreateTrigger: o ref + se foi recém-criado ou reaproveitado. */
362
+ interface FindOrCreateResult extends TriggerRef {
363
+ created: boolean;
364
+ }
365
+ /** Trigger como listado/obtido do BE. */
366
+ interface TriggerInfo {
367
+ id: string;
368
+ agentId?: string;
369
+ name?: string;
370
+ description?: string;
371
+ triggerType?: string;
372
+ enabled?: boolean;
373
+ triggerCount?: number;
374
+ lastError?: string;
375
+ /** Presente para triggers do tipo webhook. */
376
+ webhookUrl?: string;
377
+ }
378
+ interface ListTriggersParams {
379
+ /** Filtra por agente. */
380
+ agentId?: string;
381
+ /** Filtra por tipo (webhook | db_event | email | schedule). */
382
+ triggerType?: string;
383
+ /** Apenas habilitados. */
384
+ enabledOnly?: boolean;
385
+ signal?: AbortSignal;
386
+ }
387
+ interface GetTriggerParams {
388
+ triggerId: string;
389
+ signal?: AbortSignal;
390
+ }
391
+ interface DeleteTriggerParams {
392
+ triggerId: string;
393
+ signal?: AbortSignal;
394
+ }
395
+ interface DispatchAgentParams {
396
+ triggerId: string;
397
+ /** Payload entregue ao trigger (a mensagem sai do `queryField` configurado). */
398
+ payload: Record<string, unknown>;
399
+ signal?: AbortSignal;
400
+ }
401
+ interface DispatchResult {
402
+ /**
403
+ * true quando o BE aceitou o disparo. Um turno que FALHOU não chega aqui: o BE responde 200
404
+ * com `{ success:false }` e o núcleo lança — antes isso voltava como disparo aceito com
405
+ * resposta vazia.
406
+ */
407
+ accepted: boolean;
408
+ triggerId?: string;
409
+ agentId?: string;
410
+ executionId?: string;
411
+ /** Presente quando o trigger é `sync`: resposta imediata do agente. */
412
+ agentResponse?: string;
413
+ usage?: Usage;
414
+ /** Iterações do laço de tools no turno (sync). */
415
+ iterations?: number;
416
+ /** Latência medida pelo BE, ponta a ponta (sync). */
417
+ latencyMs?: number;
418
+ /**
419
+ * O texto que o BE extraiu do payload e entregou ao agente. É o campo que denuncia um
420
+ * `query_extraction` mal configurado — mensagem vazia, ou o payload inteiro virando a pergunta.
421
+ */
422
+ queryExtracted?: string;
423
+ raw: unknown;
424
+ }
425
+ interface ExecutionStatus {
426
+ executionId: string;
427
+ /**
428
+ * Status cru do BE. Além de `completed`/`failed`, o `trigger_executions` passou a registrar
429
+ * `skipped`, `quota_exhausted` e `timeout` de verdade (agosto/2026) — não trate como enum
430
+ * fechado; use `terminal` para decidir quando parar de esperar.
431
+ */
432
+ status: string;
433
+ /**
434
+ * false só enquanto a execução está em andamento. Qualquer status desconhecido conta como
435
+ * terminal, para que o polling pare em vez de girar para sempre num estado novo do BE.
436
+ */
437
+ terminal: boolean;
438
+ startedAt?: string;
439
+ finishedAt?: string;
440
+ executionTimeMs?: number;
441
+ usage?: Usage;
442
+ result?: string;
443
+ raw: unknown;
444
+ }
445
+ interface GetExecutionParams {
446
+ triggerId: string;
447
+ executionId: string;
448
+ signal?: AbortSignal;
449
+ }
450
+ interface DelegateTaskParams {
451
+ /** Agente que vai executar. */
452
+ agentId: string;
453
+ /** O que o agente deve fazer (obrigatório; sofre guardrail de tamanho por tier no BE). */
454
+ instruction: string;
455
+ /** Título legível para o board. */
456
+ title?: string;
457
+ /** Payload congelado junto da task. Objeto, **≤ 64KB** (teto do BE). */
458
+ payload?: Record<string, unknown>;
459
+ /**
460
+ * Quando executar. Omita para disparar imediatamente. Não pode estar no passado (o BE tolera
461
+ * 60s de diferença de relógio).
462
+ */
463
+ scheduledFor?: string | Date;
464
+ /** Prazo. Precisa ser futuro e posterior a `scheduledFor`; ao vencer, o sweep aborta o turno. */
465
+ deadlineAt?: string | Date;
466
+ retryPolicy?: Record<string, unknown>;
467
+ notify?: Record<string, unknown>;
468
+ /** Encadeia esta task sob outra (árvore parent/root). */
469
+ parentTaskId?: string;
470
+ signal?: AbortSignal;
471
+ }
472
+ /** Task como o BE a devolve. Campos além dos mapeados ficam em `raw`. */
473
+ interface TaskInfo {
474
+ id: string;
475
+ agentId?: string;
476
+ title?: string;
477
+ instruction?: string;
478
+ /** `pending` → `running` → `completed` | `failed` | `cancelled` | `expired`. Não é enum fechado. */
479
+ status: string;
480
+ /**
481
+ * false só enquanto a task está em andamento. Status desconhecido conta como terminal (parar é
482
+ * melhor que girar para sempre); status ausente conta como não-terminal (não lemos o envelope).
483
+ */
484
+ terminal: boolean;
485
+ scheduledFor?: string;
486
+ deadlineAt?: string;
487
+ parentTaskId?: string;
488
+ rootTaskId?: string;
489
+ sessionId?: string;
490
+ createdAt?: string;
491
+ updatedAt?: string;
492
+ raw: unknown;
493
+ }
494
+ interface ListTasksParams {
495
+ /** Só estados vivos (board do dia a dia). */
496
+ live?: boolean;
497
+ status?: string;
498
+ agentId?: string;
499
+ rootTaskId?: string;
500
+ limit?: number;
501
+ offset?: number;
502
+ signal?: AbortSignal;
503
+ }
504
+ interface TaskListPage {
505
+ items: TaskInfo[];
506
+ /** Total do tenant para a query. Vem de `meta.total` no envelope novo. */
507
+ total?: number;
508
+ raw: unknown;
509
+ }
510
+ /** Uma execução da task (uma task pode ter várias, com retry). */
511
+ interface TaskExecution {
512
+ id: string;
513
+ status: string;
514
+ exitReason?: string;
515
+ modelUsed?: string;
516
+ totalCostUsd?: number;
517
+ executionTimeMs?: number;
518
+ iterations?: number;
519
+ startedAt?: string;
520
+ finishedAt?: string;
521
+ raw: unknown;
522
+ }
523
+ interface TaskDossier {
524
+ task: TaskInfo;
525
+ executions: TaskExecution[];
526
+ children: TaskInfo[];
527
+ approvals: unknown[];
528
+ raw: unknown;
529
+ }
530
+ interface GetTaskParams {
531
+ taskId: string;
532
+ signal?: AbortSignal;
533
+ }
534
+ interface CancelTaskParams {
535
+ taskId: string;
536
+ /** Registrado na trilha. Default do BE: `user_cancel`. */
537
+ reason?: string;
538
+ signal?: AbortSignal;
539
+ }
540
+ interface CancelTaskResult {
541
+ task: TaskInfo;
542
+ /** true quando havia um turno em voo que foi realmente derrubado. */
543
+ aborted: boolean;
544
+ raw: unknown;
545
+ }
546
+
547
+ /**
548
+ * Transporte HTTP do núcleo: resolução/validação de base URL (SSRF), headers de auth,
549
+ * deadline via AbortController que cobre TODO o request (headers + corpo/stream),
550
+ * e retry com exponential backoff + jitter.
551
+ *
552
+ * Política de retry (evita duplicar efeito colateral):
553
+ * - 'idempotent' (GET): retenta em erro de rede, 429 e 5xx.
554
+ * - 'side-effecting' (POST: invoke/dispatch/create): retenta SÓ em 429/503 (servidor disse
555
+ * "tente depois", sem ter processado). NUNCA em erro de rede — o request pode ter sido
556
+ * recebido e processado mesmo com o socket caindo na volta.
557
+ *
558
+ * @module http
559
+ */
560
+
561
+ /**
562
+ * Deadline default POR OPERAÇÃO. Cada valor fica ACIMA do orçamento do BE para aquela rota,
563
+ * porque o BE foi desenhado para entregar o desfecho ao cliente — desistir antes dele troca um
564
+ * erro estruturado (TURN_TIMEOUT com execution_id/session_id) por um abort cego nosso.
565
+ *
566
+ * Verificado contra o BE (`zihin-agent-builder/server-llm`) em 2026-08-31:
567
+ * - `stream` POST /api/v2/agents/:id/stream — o SSE fecha em 180s (`agents-v2.js:690`) e o turno
568
+ * do canal `chat` morre em 150s (`turn-deadline.js`). O stream sobrevive ao turno DE
569
+ * PROPÓSITO, para entregar o desfecho; 200s deixa essa cauda chegar.
570
+ * - `dispatch` POST /api/triggers/webhook/:id — fora do timeout global do Express
571
+ * (`express.js:71`); turno de canal `async` = 240s (`turn-deadline.js`).
572
+ * - `short` GETs de listagem/telemetria e CRUD de trigger — timeout global de 30s
573
+ * (`express.js:40`), que responde 408. Esperar mais que isso é esperar à toa.
574
+ *
575
+ * Mudou o BE? Atualize esta tabela e `docs/CONTRATO-INVOCACAO-AGENTE.md` juntos.
576
+ */
577
+ declare const OPERATION_TIMEOUT_MS: Readonly<{
578
+ stream: 200000;
579
+ dispatch: 260000;
580
+ short: 35000;
581
+ }>;
582
+ type TimeoutOperation = keyof typeof OPERATION_TIMEOUT_MS;
583
+ /**
584
+ * Folga somada ao `options.timeoutMs` do chamador para formar o deadline LOCAL. O mesmo número vai
585
+ * ao BE como `options.timeout_ms` (o teto que ele deve respeitar); sem folga, os dois relógios
586
+ * disparam juntos e o erro do servidor — que tem request_id — perde a corrida para o nosso abort.
587
+ */
588
+ declare const DEADLINE_GRACE_MS = 10000;
589
+
590
+ /**
591
+ * Listagem de agentes do tenant (GET /api/v2/agents). Alimenta o dropdown do nó n8n (#17)
592
+ * e o select de `agentId` no Paperclip (#20). Aceita key `zhn_live_*` (agents:read em todos os roles).
593
+ *
594
+ * @module agents
595
+ */
596
+
597
+ interface AgentSummary {
598
+ id: string;
599
+ name?: string;
600
+ commercialName?: string;
601
+ bio?: string;
602
+ type?: string;
603
+ status?: string;
604
+ tags?: string[];
605
+ toolsCount?: number;
606
+ }
607
+
608
+ /**
609
+ * ZihinAgentClient — fachada do núcleo. Carrega a config uma vez (auth, baseUrl, timeout,
610
+ * retries) e expõe os dois caminhos do padrão híbrido:
611
+ * • Direto: invokeAgent / streamAgent (POST /stream, SSE bufferizado)
612
+ * • Triggers: createTrigger / dispatchAgent / getExecution (async + callback)
613
+ * • Auxiliar: listAgents (dropdown de agentes)
614
+ *
615
+ * @module client
616
+ */
617
+
618
+ declare class ZihinAgentClient {
619
+ private readonly cfg;
620
+ constructor(config: ZihinClientConfig);
621
+ /** Base URL efetiva (após validação SSRF). */
622
+ get baseUrl(): string;
623
+ /** Notifica `config.onError` (falha definitiva); o callback nunca derruba o fluxo. */
624
+ private report;
625
+ private guard;
626
+ /** Invoca o agente e retorna o resultado final consolidado (response + metrics). */
627
+ invokeAgent(params: InvokeAgentParams): Promise<AgentResult>;
628
+ /** Emite deltas ao vivo do agente (async iterator). */
629
+ streamAgent(params: InvokeAgentParams): AsyncGenerator<AgentStreamChunk>;
630
+ /** Provisiona (1x) um trigger de webhook ligado a um agente. */
631
+ createTrigger(params: CreateTriggerParams): Promise<TriggerRef>;
632
+ /** Reaproveita (por agente+nome) ou cria um trigger — evita duplicação. */
633
+ findOrCreateTrigger(params: CreateTriggerParams): Promise<FindOrCreateResult>;
634
+ /** Lista os triggers do tenant (filtros opcionais). */
635
+ listTriggers(params?: ListTriggersParams): Promise<TriggerInfo[]>;
636
+ /** Obtém um trigger pelo id (inclui webhookUrl). */
637
+ getTrigger(params: GetTriggerParams): Promise<TriggerInfo>;
638
+ /** Atualiza um trigger (name/description/enabled/extração/callback) via PATCH. */
639
+ updateTrigger(params: UpdateTriggerParams): Promise<TriggerInfo>;
640
+ /** Remove um trigger (requer key admin/owner). */
641
+ deleteTrigger(params: DeleteTriggerParams): Promise<{
642
+ deleted: boolean;
643
+ }>;
644
+ /** Dispara um trigger: 202 (async) ou JSON imediato (sync). */
645
+ dispatchAgent(params: DispatchAgentParams): Promise<DispatchResult>;
646
+ /** Consulta o resultado de uma execução disparada por trigger (polling). */
647
+ getExecution(params: GetExecutionParams): Promise<ExecutionStatus>;
648
+ /**
649
+ * Delega uma tarefa a um agente (requer key owner/admin). Sem `scheduledFor`, o BE dispara na
650
+ * hora fora do request e devolve só o handle — acompanhe com `getTask`.
651
+ */
652
+ delegateTask(params: DelegateTaskParams): Promise<TaskInfo>;
653
+ /** Board de tasks do tenant (requer key owner/admin/editor). */
654
+ listTasks(params?: ListTasksParams): Promise<TaskListPage>;
655
+ /** Dossiê de uma task: task + execuções + filhos + aprovações. */
656
+ getTask(params: GetTaskParams): Promise<TaskDossier>;
657
+ /** Cancela uma task (requer key owner/admin); derruba o turno em voo, se houver. */
658
+ cancelTask(params: CancelTaskParams): Promise<CancelTaskResult>;
659
+ /** Lista os agentes do tenant (dropdown). */
660
+ listAgents(opts?: {
661
+ signal?: AbortSignal;
662
+ }): Promise<AgentSummary[]>;
663
+ }
664
+ /** Açúcar: cria um cliente a partir da config. */
665
+ declare function createClient(config: ZihinClientConfig): ZihinAgentClient;
666
+
667
+ /**
668
+ * Security utilities — portado de n8n-nodes-zihin/src/utils/security.ts (zero-dep).
669
+ * SSRF prevention, payload limits, error sanitization, API-key format.
670
+ * (A assinatura HMAC vive em `signing.ts` — precisa de ZihinAgentError e criaria ciclo aqui.)
671
+ *
672
+ * Núcleo compartilhado: os shells (n8n, Paperclip) reusam estas primitivas via @zihin/agent-client.
673
+ *
674
+ * @module security
675
+ */
676
+ interface UrlValidationResult {
677
+ valid: boolean;
678
+ error?: string;
679
+ sanitizedUrl?: string;
680
+ }
681
+ interface UrlValidationOptions {
682
+ allowCustomDomains?: boolean;
683
+ allowHttp?: boolean;
684
+ allowLocalhost?: boolean;
685
+ }
686
+ /**
687
+ * Validates a base URL for SSRF prevention.
688
+ *
689
+ * NOTA (S4): a checagem é por NOME de host, não pelo IP resolvido. Com `allowCustomDomains: true`,
690
+ * um host que resolve para IP privado (DNS rebinding) passa. Em ambiente sensível, mantenha o
691
+ * whitelist (default) ou faça a resolução/checagem de IP na borda de rede.
692
+ *
693
+ * @param url - The URL to validate
694
+ * @param options - Validation options
695
+ * @returns Validation result with sanitized URL or error
696
+ */
697
+ declare function validateBaseUrl(url: string, options?: UrlValidationOptions): UrlValidationResult;
698
+ interface PayloadLimits {
699
+ maxPromptBytes: number;
700
+ maxMessageCount: number;
701
+ maxSingleMessageBytes: number;
702
+ maxTotalPayloadBytes: number;
703
+ }
704
+ declare const DEFAULT_LIMITS: PayloadLimits;
705
+ interface PayloadValidationResult {
706
+ valid: boolean;
707
+ error?: string;
708
+ truncated?: boolean;
709
+ details?: {
710
+ promptBytes?: number;
711
+ messageCount?: number;
712
+ largestMessageBytes?: number;
713
+ totalBytes?: number;
714
+ };
715
+ }
716
+ /**
717
+ * Validates a single message/prompt size against configured limits.
718
+ */
719
+ declare function validatePromptSize(prompt: string, limits?: Partial<PayloadLimits>): PayloadValidationResult;
720
+ interface SanitizedError {
721
+ message: string;
722
+ code?: string;
723
+ retryable: boolean;
724
+ retryAfter?: number;
725
+ }
726
+ /**
727
+ * Sanitizes API errors to prevent information leakage (keys, request IDs, stack traces).
728
+ */
729
+ declare function sanitizeApiError(error: unknown): SanitizedError;
730
+ interface ApiKeyValidationResult {
731
+ valid: boolean;
732
+ error?: string;
733
+ environment?: 'live' | 'test' | 'dev';
734
+ }
735
+ /**
736
+ * Validates API key format (zhn_live_* | zhn_test_* | zhn_dev_*).
737
+ */
738
+ declare function validateApiKeyFormat(apiKey: string): ApiKeyValidationResult;
739
+
740
+ /**
741
+ * Assinatura HMAC opcional de payloads (auth `signature` dos triggers).
742
+ *
743
+ * Vive fora de `security.ts` de propósito: precisa de `ZihinAgentError` (errors.ts), e errors.ts
744
+ * já importa security.ts — juntar os dois criaria um ciclo de import.
745
+ *
746
+ * @module signing
747
+ */
748
+ interface SignedRequestHeaders {
749
+ 'X-Zihin-Signature': string;
750
+ 'X-Zihin-Timestamp': string;
751
+ 'X-Zihin-Nonce': string;
752
+ }
753
+ /**
754
+ * Assina um payload com HMAC-SHA256 (timestamp + nonce + payload).
755
+ *
756
+ * ⚠️ No build ESM depende de `process.getBuiltinModule` (Node >=20.16); em Node mais antigo via
757
+ * `import` lança `ZihinAgentError` com `kind: 'config'`. O build CJS funciona em qualquer Node
758
+ * >=18. (Todo o resto do núcleo é Web-API puro e roda nos dois.)
759
+ */
760
+ declare function signRequest(payload: string, secretKey: string, timestamp?: number): SignedRequestHeaders;
761
+
762
+ /**
763
+ * Caminho C — delegação assíncrona durável (`/api/v1/tasks`).
764
+ *
765
+ * Diferente do Caminho B (triggers), aqui a conexão pode morrer sem levar o trabalho junto: a
766
+ * task é persistida (`agent_tasks`), executada fora do request e o resultado é notificado. É o
767
+ * caminho para runs que estouram o teto do host — o passo do Zapier morre em ~30s, e o próprio
768
+ * app marca isso como pendência desde o primeiro commit.
769
+ *
770
+ * Contrato verificado em `routes/tasks.js` (2026-08-31) — ver o Caminho C de
771
+ * `docs/CONTRATO-INVOCACAO-AGENTE.md`. Três coisas que diferem do resto da API e custaram
772
+ * retrabalho quando supostas em vez de lidas:
773
+ * 1. O envelope é o canônico novo: `{ success, data, meta:{request_id} }`. O `request_id` do
774
+ * sucesso vive em `meta`, não no topo.
775
+ * 2. O RBAC é mais estreito do que "admin/editor": delegar e cancelar exigem **owner|admin**;
776
+ * editor só lê o board e o dossiê.
777
+ * 3. Sem `scheduled_for` o BE dispara na hora, fora do request (`setImmediate`), e a resposta
778
+ * devolve só o handle — por isso estas rotas são todas de orçamento curto.
779
+ *
780
+ * @module tasks
781
+ */
782
+
783
+ /**
784
+ * Teto do `payload` congelado na task. Espelha o `MAX_PAYLOAD_BYTES` do BE
785
+ * (`routes/tasks.js:26`) — validar aqui evita gastar um round-trip para receber um 400.
786
+ */
787
+ declare const MAX_TASK_PAYLOAD_BYTES: number;
788
+
789
+ export { type AgentResult, type AgentStreamChunk, type AgentSummary, type Attachment, type AttachmentType, type CancelTaskParams, type CancelTaskResult, 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 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 TimeoutOperation, type TriggerInfo, type TriggerMode, type TriggerRef, type UpdateTriggerParams, type Usage, ZihinAgentClient, ZihinAgentError, type ZihinClientConfig, type ZihinErrorKind, createClient, isCommercialBlock, sanitizeApiError, signRequest, validateApiKeyFormat, validateBaseUrl, validatePromptSize };