@zihin/agent-client 0.4.1 → 0.5.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 Aionz
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -166,6 +166,29 @@ parcial. Se foi timeout ou não vem do campo **explícito `error.timed_out`** do
166
166
  (`/TIMEOUT/i` no `code`), que será removido numa major futura. Não ramifique no **nome** do código
167
167
  de timeout: o BE vai renomeá-los (zihin-agent-builder#561); use `kind`.
168
168
 
169
+ ### Turno encerrado por tempo (0.5)
170
+
171
+ Quando é o **motor** que encerra o turno por tempo, o BE não manda `error`: manda um `response` com a
172
+ mensagem de falha em `content` e fecha com `done { outcome: 'TIMEOUT', timeout_clock }`. O núcleo lê
173
+ o `done` antes do `response` e **lança** `ZihinAgentError` com `kind: 'timeout'`,
174
+ `code: 'TURN_TIMEOUT'`, `timeoutClock` (`turn` · `first_signal` · `idle` · `call`), `executionId` e
175
+ `sessionId` — não retentável, porque o turno pode ter executado tools. Quem consome `streamAgent`
176
+ direto aplica a mesma regra com `errorFromDoneChunk(chunk, { executionId, sessionId })`.
177
+
178
+ O mesmo vale para os outros desfechos em que o `content` é falha ou recusa do motor, e não
179
+ resposta: `ERROR`/`CANCELLED` (pelo servidor) → `kind: 'server'`, `QUOTA_EXCEEDED` → `quota_exceeded`,
180
+ `SUSPENDED`/`BUDGET_EXCEEDED`/`SESSION_BUSY`/`CONFLICT` e o `PAUSED` de agente pausado/arquivado →
181
+ `blocked` (o `code` é o `outcome`; só `SESSION_BUSY` é retentável). Um `ERROR` precedido de
182
+ `status.blocked` (gate da plataforma) também sai `blocked`, com o motivo em `code`
183
+ (`ownership_denied` → `not_found`).
184
+
185
+ Resposta **enlatada** de gate (atendimento humano, denylist, sessão suspensa) não lança: volta com
186
+ `AgentResult.gated: true` — nenhum modelo rodou. Num canal conversacional ela pode ir à pessoa; numa
187
+ automação, trate como "não respondido".
188
+
189
+ `AgentResult.outcome` traz o desfecho dos turnos que respondem: `COMPLETED`, `LENGTH` (com
190
+ `truncated`/`finishReason`), `PAUSED` de aprovação pendente e desfechos novos. `outcome` ausente = backend antigo.
191
+
169
192
  ## Dropdown de agentes
170
193
 
171
194
  ```ts
@@ -203,6 +226,10 @@ status, virariam "troque a key" ou "tente de novo". Têm `kind` próprio e **nun
203
226
  (`kind: 'auth'`) significa que o **papel** da key não permite a ação — e o papel exigido varia por
204
227
  rota (editor cria trigger, mas não delega task), então a mensagem não enumera papéis.
205
228
 
229
+ A exceção é o 403 com `details.reason: 'AGENT_ACCESS_DENIED'`: o agente não existe, está inativo ou
230
+ é de outro workspace (o BE não distingue, de propósito). Ele sai como `kind: 'not_found'` — o
231
+ problema é o `agentId`, não a key.
232
+
206
233
  `ZihinAgentError.executionId` vem preenchido quando o BE o entrega (sucesso e erro) — é o handle para
207
234
  correlacionar com o console.
208
235
 
@@ -213,7 +240,10 @@ correlacionar com o console.
213
240
  ACIMA do orçamento do BE para aquela rota (SSE 180s, turno async 240s, timeout global 30s), para
214
241
  que o desfecho do servidor chegue como erro estruturado em vez de abort cego do cliente.
215
242
  `timeoutMs` na config sobrepõe todos; `options.timeoutMs` por chamada vai ao BE como
216
- `timeout_ms` e o deadline local fica um pouco acima dele. Retentativas NÃO encolhem o deadline
243
+ `timeout_ms`, que o BE **honra só para encurtar** (`min(prazo da plataforma, pedido)`; o chat dá
244
+ 150s). O deadline local fica 30s acima (`DEADLINE_GRACE_MS`), porque o relógio do turno no BE só
245
+ começa depois da carga de tools/MCP. Em serverless (Lambda, Vercel, Zapier…), use `timeoutMs` ao menos 30s
246
+ abaixo do limite da plataforma, ou a plataforma mata a função antes do erro local. Retentativas NÃO encolhem o deadline
217
247
  da tentativa seguinte — o que fica limitado é o tempo gasto com retries.
218
248
  - **Retry sem duplicar efeito** — GET (idempotente) retenta em rede/429/5xx; POST com efeito
219
249
  (`invokeAgent`/`dispatchAgent`/`createTrigger`) retenta **só em 429/503** e **nunca em erro de
package/dist/index.d.mts CHANGED
@@ -4,7 +4,8 @@
4
4
  *
5
5
  * @module errors
6
6
  */
7
- type ZihinErrorKind = 'config' | 'auth' | 'trial_expired' | 'no_subscription' | 'quota_exceeded' | 'not_found' | 'rate_limit' | 'timeout' | 'aborted' | 'server' | 'network' | 'protocol' | 'unknown';
7
+
8
+ type ZihinErrorKind = 'config' | 'auth' | 'trial_expired' | 'no_subscription' | 'quota_exceeded' | 'not_found' | 'rate_limit' | 'timeout' | 'aborted' | 'server' | 'network' | 'protocol' | 'blocked' | 'unknown';
8
9
  /** Contexto opcional anexado a um erro para correlação/depuração. */
9
10
  interface ErrorMeta {
10
11
  /** Ex.: "POST llm.zihin.ai/api/v2/agents/abc/stream". */
@@ -23,6 +24,14 @@ interface ZihinErrorOptions extends ErrorMeta {
23
24
  * handoff) — é o handle que liga um erro daqui à execução no console/telemetria.
24
25
  */
25
26
  executionId?: string;
27
+ /** Sessão do turno — para o chamador retomar/inspecionar a conversa depois de um erro. */
28
+ sessionId?: string;
29
+ /**
30
+ * Só em `kind: 'timeout'` vindo do desfecho do turno (`done.timeout_clock`): qual relógio do BE
31
+ * encerrou o turno — `turn`, `first_signal`, `idle` ou `call`. Ausente num timeout de
32
+ * transporte/local, onde o turno pode nem ter terminado.
33
+ */
34
+ timeoutClock?: TimeoutClock;
26
35
  }
27
36
  declare class ZihinAgentError extends Error {
28
37
  readonly kind: ZihinErrorKind;
@@ -33,6 +42,8 @@ declare class ZihinAgentError extends Error {
33
42
  readonly attempts?: number;
34
43
  readonly endpoint?: string;
35
44
  readonly executionId?: string;
45
+ readonly sessionId?: string;
46
+ readonly timeoutClock?: TimeoutClock;
36
47
  constructor(kind: ZihinErrorKind, message: string, opts?: ZihinErrorOptions);
37
48
  }
38
49
  /**
@@ -109,7 +120,15 @@ interface Attachment {
109
120
  interface InvokeOptions {
110
121
  temperature?: number;
111
122
  maxTokens?: number;
112
- /** Timeout específico desta chamada (ms); sobrepõe o do cliente. */
123
+ /**
124
+ * Teto do TURNO desta chamada (ms); sobrepõe o do cliente. Vai ao BE como `options.timeout_ms`
125
+ * e o BE o honra (desde 30/09/2026, contrato de tempo §4), mas **só para encurtar**: o prazo
126
+ * efetivo é `min(prazo da plataforma, pedido)` — no `/stream` o canal chat dá 150s. Pedir mais
127
+ * não alonga. Um turno cortado por esse prazo sai `TIMEOUT` com `timeoutClock: 'turn'`.
128
+ *
129
+ * O relógio do turno no BE só começa DEPOIS da carga (config, tools, MCP frio). O deadline
130
+ * local soma `DEADLINE_GRACE_MS` a este valor para ainda receber o desfecho do servidor.
131
+ */
113
132
  timeoutMs?: number;
114
133
  /** Just-in-time tool loading (Sprint 33). */
115
134
  jitMode?: boolean;
@@ -175,9 +194,41 @@ interface AgentResult {
175
194
  }>;
176
195
  iterations?: number;
177
196
  executionTimeMs?: number;
197
+ /** `execution_id` do turno (evento `metrics`) — handle de correlação no console/telemetria. */
198
+ executionId?: string;
199
+ /**
200
+ * `true` = o `content` é a resposta ENLATADA de um gate da plataforma (conversa em atendimento
201
+ * humano, consumidor bloqueado, sessão suspensa), não do agente: nenhum modelo rodou. Em canal
202
+ * conversacional ela pode ser entregue à pessoa; numa automação, trate como "não respondido".
203
+ */
204
+ gated?: boolean;
205
+ /**
206
+ * Desfecho do turno, lido do `done` (`exitReason` do motor). `undefined` = backend antigo ou
207
+ * stream cortado antes do `done`. Só chegam aqui os desfechos cujo `content` é resposta
208
+ * (`COMPLETED`, `LENGTH`), o `PAUSED` (aprovação pendente ou agente pausado) e desfechos novos.
209
+ * `TIMEOUT`, `ERROR`, `CANCELLED`, `SUSPENDED` e os de bloqueio viram `ZihinAgentError` — o
210
+ * `content` desses turnos é a mensagem de falha/recusa do motor (`errorFromDoneChunk`).
211
+ */
212
+ outcome?: AgentOutcome;
213
+ /** `true` quando a resposta foi cortada pelo teto de saída do modelo (`done.truncated`). */
214
+ truncated?: boolean;
215
+ /** Motivo do corte quando `truncated` (ex.: `length`). */
216
+ finishReason?: string;
178
217
  /** Eventos SSE brutos coletados (para depuração / casos avançados). */
179
218
  raw: AgentStreamChunk[];
180
219
  }
220
+ /**
221
+ * Desfecho do turno (`done.outcome` = `resolveTurnOutcome` do motor). `COMPLETED` e `LENGTH`
222
+ * contam como sucesso no BE (`isSuccessfulOutcome`); os demais valores são repassados como vêm.
223
+ * Aberto (`string & {}`) porque o BE pode acrescentar desfechos (ex.: os de bloqueio).
224
+ */
225
+ type AgentOutcome = 'COMPLETED' | 'LENGTH' | 'TIMEOUT' | 'ERROR' | 'CANCELLED' | 'PAUSED' | 'SUSPENDED' | 'BUDGET_EXCEEDED' | 'QUOTA_EXCEEDED' | 'SESSION_BUSY' | 'CONFLICT' | (string & {});
226
+ /**
227
+ * Qual relógio encerrou o turno (contrato de tempo §2): `turn` (prazo do turno, inclusive o
228
+ * `options.timeoutMs` pedido), `first_signal` (o provedor não começou a responder), `idle`
229
+ * (silêncio entre pedaços do stream do provedor) ou `call` (chamada ao modelo sem stream).
230
+ */
231
+ type TimeoutClock = 'turn' | 'first_signal' | 'idle' | 'call' | (string & {});
181
232
  /**
182
233
  * Chunk normalizado emitido por `streamAgent`. Mapeia os tipos de evento SSE do BE:
183
234
  * metadata, status, attachments, phase, thinking, tool, action_proposed, token,
@@ -189,9 +240,12 @@ type AgentStreamChunk = {
189
240
  agentId?: string;
190
241
  sessionId?: string;
191
242
  data: unknown;
192
- } | {
243
+ }
244
+ /** `blocked` = motivo do bloqueio do turno (`agent_paused`, `budget_exceeded`…), quando houver. */
245
+ | {
193
246
  type: 'status';
194
247
  phase?: string;
248
+ blocked?: string;
195
249
  data: unknown;
196
250
  } | {
197
251
  type: 'attachments';
@@ -250,9 +304,20 @@ type AgentStreamChunk = {
250
304
  sessionId?: string;
251
305
  data: unknown;
252
306
  }
253
- /** Fim LIMPO do stream (`utils/sse.js:92`). Sem ele, o stream foi cortado no meio. */
307
+ /**
308
+ * Fim LIMPO do stream (`utils/sse.js`). Sem ele, o stream foi cortado no meio. Carrega o
309
+ * desfecho do turno: `{ outcome, timed_out?, timeout_clock?, truncated?, finish_reason? }`
310
+ * (aditivo — backend antigo manda `{}`). Um turno que o motor encerrou por tempo chega aqui
311
+ * com `outcome: 'TIMEOUT'` (idem ERROR e os bloqueios), DEPOIS de um `response` cujo `content`
312
+ * é a mensagem de falha — use `errorFromDoneChunk` para não publicá-lo como resposta.
313
+ */
254
314
  | {
255
315
  type: 'done';
316
+ outcome?: AgentOutcome;
317
+ timedOut?: boolean;
318
+ timeoutClock?: TimeoutClock;
319
+ truncated?: boolean;
320
+ finishReason?: string;
256
321
  data: unknown;
257
322
  } | {
258
323
  type: 'response';
@@ -263,6 +328,8 @@ type AgentStreamChunk = {
263
328
  type: string;
264
329
  [k: string]: unknown;
265
330
  }>;
331
+ /** Resposta ENLATADA de um gate (handoff humano, denylist, sessão suspensa) — não do agente. */
332
+ gated?: boolean;
266
333
  data: unknown;
267
334
  } | {
268
335
  type: 'metrics';
@@ -270,6 +337,7 @@ type AgentStreamChunk = {
270
337
  model?: string;
271
338
  iterations?: number;
272
339
  executionTimeMs?: number;
340
+ executionId?: string;
273
341
  data: unknown;
274
342
  } | {
275
343
  type: 'unknown';
@@ -589,8 +657,13 @@ type TimeoutOperation = keyof typeof OPERATION_TIMEOUT_MS;
589
657
  * Folga somada ao `options.timeoutMs` do chamador para formar o deadline LOCAL. O mesmo número vai
590
658
  * ao BE como `options.timeout_ms` (o teto que ele deve respeitar); sem folga, os dois relógios
591
659
  * disparam juntos e o erro do servidor — que tem request_id — perde a corrida para o nosso abort.
660
+ *
661
+ * 30s, não 10s: o relógio do turno no BE só começa DEPOIS da carga (config, tools, gates — um MCP
662
+ * frio leva dezenas de segundos), então o tempo de parede visto daqui é `carga + prazo` (contrato
663
+ * de tempo §4). Com 10s, um `timeoutMs: 60000` num agente com MCP abortava aos 70s e trocava o
664
+ * `done { outcome: 'TIMEOUT' }` do servidor por um abort cego.
592
665
  */
593
- declare const DEADLINE_GRACE_MS = 10000;
666
+ declare const DEADLINE_GRACE_MS = 30000;
594
667
 
595
668
  /**
596
669
  * Listagem de agentes do tenant (GET /api/v2/agents). Alimenta o dropdown do nó n8n (#17)
@@ -698,6 +771,28 @@ declare function classifyStreamErrorKind(code: string, timedOut?: boolean): 'tim
698
771
  declare function errorFromStreamChunk(chunk: Extract<AgentStreamChunk, {
699
772
  type: 'error';
700
773
  }>): ZihinAgentError;
774
+ /**
775
+ * Converte o `done` de um turno que NÃO produziu resposta num `ZihinAgentError`; devolve
776
+ * `undefined` quando o `content` é resposta. Mesma regra do `invokeAgent`, exportada para quem
777
+ * consome `streamAgent` direto (o Paperclip) não reimplementá-la.
778
+ *
779
+ * Por que erro e não um campo no resultado: nesses turnos o BE já emitiu um `response` com a
780
+ * mensagem de falha do motor em `content` (contrato de tempo §6). Como campo, cada shell teria
781
+ * de lembrar de checá-lo — e o que esquecesse publicaria a falha como resposta, que é o defeito
782
+ * que isto fecha (#81). Como erro, o caminho seguro é o default.
783
+ *
784
+ * TIMEOUT: `outcome: 'TIMEOUT'` é a fonte primária; `timed_out: true` sozinho também conta (o BE
785
+ * só o emite com TIMEOUT). Vira `kind: 'timeout'`, `code: 'TURN_TIMEOUT'`, com `timeoutClock`.
786
+ * Nenhum é retentável por default: o turno pode ter executado tools com efeito colateral
787
+ * (exceção: `SESSION_BUSY`, em que o turno nem começou).
788
+ */
789
+ declare function errorFromDoneChunk(chunk: Extract<AgentStreamChunk, {
790
+ type: 'done';
791
+ }>, ctx?: {
792
+ executionId?: string;
793
+ sessionId?: string;
794
+ blocked?: string;
795
+ }): ZihinAgentError | undefined;
701
796
 
702
797
  /**
703
798
  * Security utilities — portado de n8n-nodes-zihin/src/utils/security.ts (zero-dep).
@@ -821,4 +916,4 @@ declare function signRequest(payload: string, secretKey: string, timestamp?: num
821
916
  */
822
917
  declare const MAX_TASK_PAYLOAD_BYTES: number;
823
918
 
824
- 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, classifyStreamErrorKind, createClient, errorFromStreamChunk, isCommercialBlock, sanitizeApiError, signRequest, validateApiKeyFormat, validateBaseUrl, validatePromptSize };
919
+ export { type AgentOutcome, 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 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 };
package/dist/index.d.ts CHANGED
@@ -4,7 +4,8 @@
4
4
  *
5
5
  * @module errors
6
6
  */
7
- type ZihinErrorKind = 'config' | 'auth' | 'trial_expired' | 'no_subscription' | 'quota_exceeded' | 'not_found' | 'rate_limit' | 'timeout' | 'aborted' | 'server' | 'network' | 'protocol' | 'unknown';
7
+
8
+ type ZihinErrorKind = 'config' | 'auth' | 'trial_expired' | 'no_subscription' | 'quota_exceeded' | 'not_found' | 'rate_limit' | 'timeout' | 'aborted' | 'server' | 'network' | 'protocol' | 'blocked' | 'unknown';
8
9
  /** Contexto opcional anexado a um erro para correlação/depuração. */
9
10
  interface ErrorMeta {
10
11
  /** Ex.: "POST llm.zihin.ai/api/v2/agents/abc/stream". */
@@ -23,6 +24,14 @@ interface ZihinErrorOptions extends ErrorMeta {
23
24
  * handoff) — é o handle que liga um erro daqui à execução no console/telemetria.
24
25
  */
25
26
  executionId?: string;
27
+ /** Sessão do turno — para o chamador retomar/inspecionar a conversa depois de um erro. */
28
+ sessionId?: string;
29
+ /**
30
+ * Só em `kind: 'timeout'` vindo do desfecho do turno (`done.timeout_clock`): qual relógio do BE
31
+ * encerrou o turno — `turn`, `first_signal`, `idle` ou `call`. Ausente num timeout de
32
+ * transporte/local, onde o turno pode nem ter terminado.
33
+ */
34
+ timeoutClock?: TimeoutClock;
26
35
  }
27
36
  declare class ZihinAgentError extends Error {
28
37
  readonly kind: ZihinErrorKind;
@@ -33,6 +42,8 @@ declare class ZihinAgentError extends Error {
33
42
  readonly attempts?: number;
34
43
  readonly endpoint?: string;
35
44
  readonly executionId?: string;
45
+ readonly sessionId?: string;
46
+ readonly timeoutClock?: TimeoutClock;
36
47
  constructor(kind: ZihinErrorKind, message: string, opts?: ZihinErrorOptions);
37
48
  }
38
49
  /**
@@ -109,7 +120,15 @@ interface Attachment {
109
120
  interface InvokeOptions {
110
121
  temperature?: number;
111
122
  maxTokens?: number;
112
- /** Timeout específico desta chamada (ms); sobrepõe o do cliente. */
123
+ /**
124
+ * Teto do TURNO desta chamada (ms); sobrepõe o do cliente. Vai ao BE como `options.timeout_ms`
125
+ * e o BE o honra (desde 30/09/2026, contrato de tempo §4), mas **só para encurtar**: o prazo
126
+ * efetivo é `min(prazo da plataforma, pedido)` — no `/stream` o canal chat dá 150s. Pedir mais
127
+ * não alonga. Um turno cortado por esse prazo sai `TIMEOUT` com `timeoutClock: 'turn'`.
128
+ *
129
+ * O relógio do turno no BE só começa DEPOIS da carga (config, tools, MCP frio). O deadline
130
+ * local soma `DEADLINE_GRACE_MS` a este valor para ainda receber o desfecho do servidor.
131
+ */
113
132
  timeoutMs?: number;
114
133
  /** Just-in-time tool loading (Sprint 33). */
115
134
  jitMode?: boolean;
@@ -175,9 +194,41 @@ interface AgentResult {
175
194
  }>;
176
195
  iterations?: number;
177
196
  executionTimeMs?: number;
197
+ /** `execution_id` do turno (evento `metrics`) — handle de correlação no console/telemetria. */
198
+ executionId?: string;
199
+ /**
200
+ * `true` = o `content` é a resposta ENLATADA de um gate da plataforma (conversa em atendimento
201
+ * humano, consumidor bloqueado, sessão suspensa), não do agente: nenhum modelo rodou. Em canal
202
+ * conversacional ela pode ser entregue à pessoa; numa automação, trate como "não respondido".
203
+ */
204
+ gated?: boolean;
205
+ /**
206
+ * Desfecho do turno, lido do `done` (`exitReason` do motor). `undefined` = backend antigo ou
207
+ * stream cortado antes do `done`. Só chegam aqui os desfechos cujo `content` é resposta
208
+ * (`COMPLETED`, `LENGTH`), o `PAUSED` (aprovação pendente ou agente pausado) e desfechos novos.
209
+ * `TIMEOUT`, `ERROR`, `CANCELLED`, `SUSPENDED` e os de bloqueio viram `ZihinAgentError` — o
210
+ * `content` desses turnos é a mensagem de falha/recusa do motor (`errorFromDoneChunk`).
211
+ */
212
+ outcome?: AgentOutcome;
213
+ /** `true` quando a resposta foi cortada pelo teto de saída do modelo (`done.truncated`). */
214
+ truncated?: boolean;
215
+ /** Motivo do corte quando `truncated` (ex.: `length`). */
216
+ finishReason?: string;
178
217
  /** Eventos SSE brutos coletados (para depuração / casos avançados). */
179
218
  raw: AgentStreamChunk[];
180
219
  }
220
+ /**
221
+ * Desfecho do turno (`done.outcome` = `resolveTurnOutcome` do motor). `COMPLETED` e `LENGTH`
222
+ * contam como sucesso no BE (`isSuccessfulOutcome`); os demais valores são repassados como vêm.
223
+ * Aberto (`string & {}`) porque o BE pode acrescentar desfechos (ex.: os de bloqueio).
224
+ */
225
+ type AgentOutcome = 'COMPLETED' | 'LENGTH' | 'TIMEOUT' | 'ERROR' | 'CANCELLED' | 'PAUSED' | 'SUSPENDED' | 'BUDGET_EXCEEDED' | 'QUOTA_EXCEEDED' | 'SESSION_BUSY' | 'CONFLICT' | (string & {});
226
+ /**
227
+ * Qual relógio encerrou o turno (contrato de tempo §2): `turn` (prazo do turno, inclusive o
228
+ * `options.timeoutMs` pedido), `first_signal` (o provedor não começou a responder), `idle`
229
+ * (silêncio entre pedaços do stream do provedor) ou `call` (chamada ao modelo sem stream).
230
+ */
231
+ type TimeoutClock = 'turn' | 'first_signal' | 'idle' | 'call' | (string & {});
181
232
  /**
182
233
  * Chunk normalizado emitido por `streamAgent`. Mapeia os tipos de evento SSE do BE:
183
234
  * metadata, status, attachments, phase, thinking, tool, action_proposed, token,
@@ -189,9 +240,12 @@ type AgentStreamChunk = {
189
240
  agentId?: string;
190
241
  sessionId?: string;
191
242
  data: unknown;
192
- } | {
243
+ }
244
+ /** `blocked` = motivo do bloqueio do turno (`agent_paused`, `budget_exceeded`…), quando houver. */
245
+ | {
193
246
  type: 'status';
194
247
  phase?: string;
248
+ blocked?: string;
195
249
  data: unknown;
196
250
  } | {
197
251
  type: 'attachments';
@@ -250,9 +304,20 @@ type AgentStreamChunk = {
250
304
  sessionId?: string;
251
305
  data: unknown;
252
306
  }
253
- /** Fim LIMPO do stream (`utils/sse.js:92`). Sem ele, o stream foi cortado no meio. */
307
+ /**
308
+ * Fim LIMPO do stream (`utils/sse.js`). Sem ele, o stream foi cortado no meio. Carrega o
309
+ * desfecho do turno: `{ outcome, timed_out?, timeout_clock?, truncated?, finish_reason? }`
310
+ * (aditivo — backend antigo manda `{}`). Um turno que o motor encerrou por tempo chega aqui
311
+ * com `outcome: 'TIMEOUT'` (idem ERROR e os bloqueios), DEPOIS de um `response` cujo `content`
312
+ * é a mensagem de falha — use `errorFromDoneChunk` para não publicá-lo como resposta.
313
+ */
254
314
  | {
255
315
  type: 'done';
316
+ outcome?: AgentOutcome;
317
+ timedOut?: boolean;
318
+ timeoutClock?: TimeoutClock;
319
+ truncated?: boolean;
320
+ finishReason?: string;
256
321
  data: unknown;
257
322
  } | {
258
323
  type: 'response';
@@ -263,6 +328,8 @@ type AgentStreamChunk = {
263
328
  type: string;
264
329
  [k: string]: unknown;
265
330
  }>;
331
+ /** Resposta ENLATADA de um gate (handoff humano, denylist, sessão suspensa) — não do agente. */
332
+ gated?: boolean;
266
333
  data: unknown;
267
334
  } | {
268
335
  type: 'metrics';
@@ -270,6 +337,7 @@ type AgentStreamChunk = {
270
337
  model?: string;
271
338
  iterations?: number;
272
339
  executionTimeMs?: number;
340
+ executionId?: string;
273
341
  data: unknown;
274
342
  } | {
275
343
  type: 'unknown';
@@ -589,8 +657,13 @@ type TimeoutOperation = keyof typeof OPERATION_TIMEOUT_MS;
589
657
  * Folga somada ao `options.timeoutMs` do chamador para formar o deadline LOCAL. O mesmo número vai
590
658
  * ao BE como `options.timeout_ms` (o teto que ele deve respeitar); sem folga, os dois relógios
591
659
  * disparam juntos e o erro do servidor — que tem request_id — perde a corrida para o nosso abort.
660
+ *
661
+ * 30s, não 10s: o relógio do turno no BE só começa DEPOIS da carga (config, tools, gates — um MCP
662
+ * frio leva dezenas de segundos), então o tempo de parede visto daqui é `carga + prazo` (contrato
663
+ * de tempo §4). Com 10s, um `timeoutMs: 60000` num agente com MCP abortava aos 70s e trocava o
664
+ * `done { outcome: 'TIMEOUT' }` do servidor por um abort cego.
592
665
  */
593
- declare const DEADLINE_GRACE_MS = 10000;
666
+ declare const DEADLINE_GRACE_MS = 30000;
594
667
 
595
668
  /**
596
669
  * Listagem de agentes do tenant (GET /api/v2/agents). Alimenta o dropdown do nó n8n (#17)
@@ -698,6 +771,28 @@ declare function classifyStreamErrorKind(code: string, timedOut?: boolean): 'tim
698
771
  declare function errorFromStreamChunk(chunk: Extract<AgentStreamChunk, {
699
772
  type: 'error';
700
773
  }>): ZihinAgentError;
774
+ /**
775
+ * Converte o `done` de um turno que NÃO produziu resposta num `ZihinAgentError`; devolve
776
+ * `undefined` quando o `content` é resposta. Mesma regra do `invokeAgent`, exportada para quem
777
+ * consome `streamAgent` direto (o Paperclip) não reimplementá-la.
778
+ *
779
+ * Por que erro e não um campo no resultado: nesses turnos o BE já emitiu um `response` com a
780
+ * mensagem de falha do motor em `content` (contrato de tempo §6). Como campo, cada shell teria
781
+ * de lembrar de checá-lo — e o que esquecesse publicaria a falha como resposta, que é o defeito
782
+ * que isto fecha (#81). Como erro, o caminho seguro é o default.
783
+ *
784
+ * TIMEOUT: `outcome: 'TIMEOUT'` é a fonte primária; `timed_out: true` sozinho também conta (o BE
785
+ * só o emite com TIMEOUT). Vira `kind: 'timeout'`, `code: 'TURN_TIMEOUT'`, com `timeoutClock`.
786
+ * Nenhum é retentável por default: o turno pode ter executado tools com efeito colateral
787
+ * (exceção: `SESSION_BUSY`, em que o turno nem começou).
788
+ */
789
+ declare function errorFromDoneChunk(chunk: Extract<AgentStreamChunk, {
790
+ type: 'done';
791
+ }>, ctx?: {
792
+ executionId?: string;
793
+ sessionId?: string;
794
+ blocked?: string;
795
+ }): ZihinAgentError | undefined;
701
796
 
702
797
  /**
703
798
  * Security utilities — portado de n8n-nodes-zihin/src/utils/security.ts (zero-dep).
@@ -821,4 +916,4 @@ declare function signRequest(payload: string, secretKey: string, timestamp?: num
821
916
  */
822
917
  declare const MAX_TASK_PAYLOAD_BYTES: number;
823
918
 
824
- 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, classifyStreamErrorKind, createClient, errorFromStreamChunk, isCommercialBlock, sanitizeApiError, signRequest, validateApiKeyFormat, validateBaseUrl, validatePromptSize };
919
+ export { type AgentOutcome, 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 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 };
package/dist/index.js CHANGED
@@ -28,6 +28,7 @@ __export(index_exports, {
28
28
  ZihinAgentError: () => ZihinAgentError,
29
29
  classifyStreamErrorKind: () => classifyStreamErrorKind,
30
30
  createClient: () => createClient,
31
+ errorFromDoneChunk: () => errorFromDoneChunk,
31
32
  errorFromStreamChunk: () => errorFromStreamChunk,
32
33
  isCommercialBlock: () => isCommercialBlock,
33
34
  sanitizeApiError: () => sanitizeApiError,
@@ -46,6 +47,12 @@ function utf8Len(s) {
46
47
  function rec(x) {
47
48
  return x && typeof x === "object" ? x : {};
48
49
  }
50
+ function safeId(x) {
51
+ return typeof x === "string" && /^[\w.:-]{1,128}$/.test(x) ? x : void 0;
52
+ }
53
+ function safeLabel(x) {
54
+ return typeof x === "string" && /^[A-Za-z0-9_]{1,64}$/.test(x) ? x : void 0;
55
+ }
49
56
  function str(x) {
50
57
  return typeof x === "string" ? x : void 0;
51
58
  }
@@ -71,8 +78,13 @@ var RUNNING_STATUSES = /* @__PURE__ */ new Set([
71
78
  "waiting",
72
79
  "retrying",
73
80
  "scheduled",
74
- "awaiting_approval"
81
+ "awaiting_approval",
75
82
  // HITL: turno pausado esperando decisão humana não terminou
83
+ // Estados VIVOS reais do backend (zihin-agent-builder `TaskService.LIVE_STATUSES` e
84
+ // `agent_executions.status`): sem eles, uma task/execução pausada ou aguardando input
85
+ // era lida como TERMINAL e o polling parava com o trabalho ainda em andamento.
86
+ "paused",
87
+ "awaiting_input"
76
88
  ]);
77
89
  function isTerminalStatus(status) {
78
90
  return !RUNNING_STATUSES.has(status.toLowerCase().replace(/-/g, "_"));
@@ -267,6 +279,9 @@ var ERROR_CODE_MAP = {
267
279
  INSUFFICIENT_CREDITS: "Insufficient credits. Please check your account balance.",
268
280
  CONTEXT_LENGTH_EXCEEDED: "The input exceeds the model context length limit.",
269
281
  AGENT_NOT_FOUND: "Agent not found or not active for this tenant.",
282
+ // Nome novo do `AGENT_NOT_FOUND` (zihin-agent-builder#561, fatia B). `AGENT_ERROR` virou
283
+ // `INTERNAL_ERROR`, que já está acima.
284
+ NOT_FOUND: "Resource not found \u2014 check the ID (agent, trigger or execution) and that it belongs to this workspace.",
270
285
  // Bloqueios comerciais. As mensagens do BE vêm em português e estes shells são em inglês;
271
286
  // além disso cada caso tem uma ação DIFERENTE, e o texto precisa dizer qual.
272
287
  TRIAL_EXPIRED: "Your Zihin trial has ended. Upgrade the plan to keep invoking agents \u2014 the API key is fine.",
@@ -337,6 +352,8 @@ var ZihinAgentError = class _ZihinAgentError extends Error {
337
352
  this.code = opts.code;
338
353
  this.requestId = opts.requestId;
339
354
  this.executionId = opts.executionId;
355
+ this.sessionId = opts.sessionId;
356
+ this.timeoutClock = opts.timeoutClock;
340
357
  this.attempts = opts.attempts;
341
358
  this.endpoint = opts.endpoint;
342
359
  this.retryable = opts.retryable ?? (kind === "rate_limit" || kind === "server" || kind === "network");
@@ -371,7 +388,7 @@ function commercialKind(body) {
371
388
  }
372
389
  function extractRequestId(body) {
373
390
  const b = rec(body);
374
- return str(b.request_id) ?? str(b.requestId) ?? str(rec(b.error).request_id) ?? str(rec(b.meta).request_id);
391
+ return safeId(b.request_id) ?? safeId(b.requestId) ?? safeId(rec(b.error).request_id) ?? safeId(rec(b.meta).request_id);
375
392
  }
376
393
  function errorFromResponse(status, body, meta = {}) {
377
394
  const sanitized = sanitizeApiError(body);
@@ -381,7 +398,7 @@ function errorFromResponse(status, body, meta = {}) {
381
398
  status,
382
399
  code,
383
400
  requestId: extractRequestId(body),
384
- executionId: str(rec(body).execution_id),
401
+ executionId: safeId(rec(body).execution_id),
385
402
  ...meta
386
403
  };
387
404
  const commercial = commercialKind(body);
@@ -392,6 +409,12 @@ function errorFromResponse(status, body, meta = {}) {
392
409
  });
393
410
  }
394
411
  if (status === 401) return new ZihinAgentError("auth", message || "Authentication failed.", base);
412
+ if (status === 403 && str(rec(rec(rec(body).error).details).reason) === "AGENT_ACCESS_DENIED")
413
+ return new ZihinAgentError(
414
+ "not_found",
415
+ "Agent not found, inactive, or not accessible with this API key \u2014 check the agent ID and that the key belongs to the same Zihin workspace.",
416
+ { ...base, retryable: false }
417
+ );
395
418
  if (status === 403)
396
419
  return new ZihinAgentError(
397
420
  "auth",
@@ -441,7 +464,7 @@ var OPERATION_TIMEOUT_MS = Object.freeze({
441
464
  dispatch: 26e4,
442
465
  short: 35e3
443
466
  });
444
- var DEADLINE_GRACE_MS = 1e4;
467
+ var DEADLINE_GRACE_MS = 3e4;
445
468
  var RETRY_OVERHEAD_MS = 6e4;
446
469
  var MIN_TIMEOUT_MS = 1e3;
447
470
  var MAX_TIMEOUT_MS = 9e5;
@@ -893,13 +916,13 @@ function mapEvent(e) {
893
916
  case "metadata":
894
917
  return {
895
918
  type: "metadata",
896
- requestId: str(d.request_id),
919
+ requestId: safeId(d.request_id),
897
920
  agentId: str(d.agent_id),
898
- sessionId: str(d.session_id),
921
+ sessionId: safeId(d.session_id),
899
922
  data: e.data
900
923
  };
901
924
  case "status":
902
- return { type: "status", phase: str(d.phase), data: e.data };
925
+ return { type: "status", phase: str(d.phase), blocked: safeLabel(d.blocked), data: e.data };
903
926
  case "attachments":
904
927
  return { type: "attachments", data: e.data };
905
928
  case "phase":
@@ -921,16 +944,24 @@ function mapEvent(e) {
921
944
  case "warning":
922
945
  return { type: "warning", message: str(d.message), data: e.data };
923
946
  case "session":
924
- return { type: "session", sessionId: str(d.session_id), data: e.data };
947
+ return { type: "session", sessionId: safeId(d.session_id), data: e.data };
925
948
  case "done":
926
- return { type: "done", data: e.data };
949
+ return {
950
+ type: "done",
951
+ outcome: safeLabel(d.outcome),
952
+ timedOut: typeof d.timed_out === "boolean" ? d.timed_out : void 0,
953
+ timeoutClock: safeLabel(d.timeout_clock),
954
+ truncated: typeof d.truncated === "boolean" ? d.truncated : void 0,
955
+ finishReason: str(d.finish_reason),
956
+ data: e.data
957
+ };
927
958
  case "error": {
928
959
  const err = rec(d.error);
929
960
  return {
930
961
  type: "error",
931
- code: str(err.code),
962
+ code: safeLabel(err.code),
932
963
  message: str(err.message),
933
- requestId: str(err.request_id),
964
+ requestId: safeId(err.request_id),
934
965
  timedOut: typeof err.timed_out === "boolean" ? err.timed_out : void 0,
935
966
  data: e.data
936
967
  };
@@ -939,7 +970,8 @@ function mapEvent(e) {
939
970
  return {
940
971
  type: "response",
941
972
  content: str(d.content) ?? "",
942
- sessionId: str(d.session_id),
973
+ sessionId: safeId(d.session_id),
974
+ gated: d.gated === true ? true : void 0,
943
975
  sources: Array.isArray(d.sources) ? d.sources : void 0,
944
976
  blocks: Array.isArray(d.blocks) ? d.blocks : void 0,
945
977
  data: e.data
@@ -952,6 +984,7 @@ function mapEvent(e) {
952
984
  model: str(modelConfig.model) ?? str(d.model),
953
985
  iterations: num(d.iterations),
954
986
  executionTimeMs: num(d.execution_time_ms),
987
+ executionId: safeId(d.execution_id),
955
988
  data: e.data
956
989
  };
957
990
  }
@@ -1064,6 +1097,77 @@ function errorFromStreamChunk(chunk) {
1064
1097
  retryable: false
1065
1098
  });
1066
1099
  }
1100
+ var FAILED_OUTCOMES = {
1101
+ ERROR: { kind: "server", message: "The agent turn failed on the server; its text is the engine failure notice, not an answer." },
1102
+ // `server`, não `aborted`: `aborted` é o cancelamento do PRÓPRIO chamador, que `onError` manda
1103
+ // filtrar — um cancelamento feito pelo operador sumiria junto.
1104
+ CANCELLED: { kind: "server", message: "The agent turn was cancelled on the server (by an operator or a platform guard)." },
1105
+ // Modo processo drenando/encerrando; o motor retoma depois. Nenhum canal atual o produz.
1106
+ SUSPENDED: { kind: "blocked", message: "The agent turn was suspended by the server and did not answer; retry later." },
1107
+ BUDGET_EXCEEDED: { kind: "blocked", message: "The agent's spending cap was reached; raise the agent budget to keep invoking it." },
1108
+ QUOTA_EXCEEDED: { kind: "quota_exceeded", message: "Your Zihin token quota is exhausted. Upgrade the plan or wait for the next cycle." },
1109
+ SESSION_BUSY: {
1110
+ kind: "blocked",
1111
+ message: "Another turn is still running in this session; retry after it finishes.",
1112
+ retryable: true
1113
+ },
1114
+ // Hoje a rota manda CONFLICT como `event: error` (não chega ao `done`); mapeado por defesa.
1115
+ CONFLICT: {
1116
+ kind: "blocked",
1117
+ message: "The declared consumer identity does not match the one already bound to this session."
1118
+ }
1119
+ };
1120
+ var PAUSED_BLOCKS = /* @__PURE__ */ new Set(["agent_paused", "agent_archived"]);
1121
+ var ERROR_BLOCKS = {
1122
+ ownership_denied: {
1123
+ kind: "not_found",
1124
+ message: "Session not found for this API key \u2014 start a new conversation (omit sessionId) instead of reusing this one."
1125
+ },
1126
+ session_load_failed: { kind: "blocked", message: "The server could not load this conversation right now; retry shortly.", retryable: true },
1127
+ approval_resolve_failed: { kind: "blocked", message: "The server could not route the required approval right now; retry shortly.", retryable: true },
1128
+ approval_no_approver: { kind: "blocked", message: "This action requires approval, but the approval policy has no approver configured." },
1129
+ blocked_schedule: { kind: "blocked", message: "The agent is outside its allowed schedule (security policy)." },
1130
+ owner_inactive: { kind: "blocked", message: "The agent's owner is inactive; the agent cannot be invoked." }
1131
+ };
1132
+ function own(table, key) {
1133
+ return key !== void 0 && Object.prototype.hasOwnProperty.call(table, key) ? table[key] : void 0;
1134
+ }
1135
+ function errorFromDoneChunk(chunk, ctx = {}) {
1136
+ const meta = { executionId: ctx.executionId, sessionId: ctx.sessionId };
1137
+ const blockedReason = safeLabel(ctx.blocked);
1138
+ if (chunk.outcome === "TIMEOUT" || chunk.timedOut === true) {
1139
+ const clock = chunk.timeoutClock;
1140
+ const what = clock === "turn" ? "the turn deadline" : clock === "first_signal" ? "the model provider did not start responding in time" : clock === "idle" ? "the model provider stream went silent" : clock === "call" ? "the model call took too long" : "a server-side clock";
1141
+ return new ZihinAgentError("timeout", `The agent turn timed out (${what}).`, {
1142
+ ...meta,
1143
+ code: "TURN_TIMEOUT",
1144
+ retryable: false,
1145
+ timeoutClock: clock
1146
+ });
1147
+ }
1148
+ if (chunk.outcome === "PAUSED" && blockedReason && PAUSED_BLOCKS.has(blockedReason)) {
1149
+ return new ZihinAgentError("blocked", "The agent is paused or archived and did not answer.", {
1150
+ ...meta,
1151
+ code: "PAUSED",
1152
+ retryable: false
1153
+ });
1154
+ }
1155
+ if (chunk.outcome === "ERROR" && blockedReason) {
1156
+ const gate = own(ERROR_BLOCKS, blockedReason);
1157
+ return new ZihinAgentError(gate?.kind ?? "blocked", gate?.message ?? `The agent turn was blocked by the server (${blockedReason}).`, {
1158
+ ...meta,
1159
+ code: blockedReason.toUpperCase(),
1160
+ retryable: gate?.retryable ?? false
1161
+ });
1162
+ }
1163
+ const failed = own(FAILED_OUTCOMES, chunk.outcome);
1164
+ if (!failed) return void 0;
1165
+ return new ZihinAgentError(failed.kind, failed.message, {
1166
+ ...meta,
1167
+ code: chunk.outcome,
1168
+ retryable: failed.retryable ?? false
1169
+ });
1170
+ }
1067
1171
  async function invokeAgent(cfg, params) {
1068
1172
  const raw = [];
1069
1173
  const keepRaw = params.includeRaw === true;
@@ -1071,7 +1175,8 @@ async function invokeAgent(cfg, params) {
1071
1175
  let metricsChunk;
1072
1176
  let metadataSessionId;
1073
1177
  let mintedSessionId;
1074
- let cleanEnd = false;
1178
+ let doneChunk;
1179
+ let blocked;
1075
1180
  let tokenBuffer = "";
1076
1181
  for await (const chunk of streamAgent(cfg, params)) {
1077
1182
  if (keepRaw) raw.push(chunk);
@@ -1083,8 +1188,14 @@ async function invokeAgent(cfg, params) {
1083
1188
  else if (chunk.type === "token") tokenBuffer += chunk.content;
1084
1189
  else if (chunk.type === "metadata" && chunk.sessionId) metadataSessionId = chunk.sessionId;
1085
1190
  else if (chunk.type === "session" && chunk.sessionId) mintedSessionId = chunk.sessionId;
1086
- else if (chunk.type === "done") cleanEnd = true;
1087
- }
1191
+ else if (chunk.type === "done") doneChunk = chunk;
1192
+ else if (chunk.type === "status" && chunk.blocked) blocked = chunk.blocked;
1193
+ }
1194
+ const sessionId = mintedSessionId ?? responseChunk?.sessionId ?? metadataSessionId ?? params.sessionId;
1195
+ const executionId = metricsChunk?.executionId;
1196
+ const outcomeError = doneChunk && errorFromDoneChunk(doneChunk, { executionId, sessionId, blocked });
1197
+ if (outcomeError) throw outcomeError;
1198
+ const cleanEnd = doneChunk !== void 0;
1088
1199
  if (!responseChunk) {
1089
1200
  if (cleanEnd) {
1090
1201
  throw new ZihinAgentError("protocol", "Stream ended (done) without a `response` event.");
@@ -1093,11 +1204,12 @@ async function invokeAgent(cfg, params) {
1093
1204
  return {
1094
1205
  content: tokenBuffer,
1095
1206
  contentSource: "tokens",
1096
- sessionId: mintedSessionId ?? metadataSessionId ?? params.sessionId,
1207
+ sessionId,
1097
1208
  usage: metricsChunk?.usage,
1098
1209
  model: metricsChunk?.model,
1099
1210
  iterations: metricsChunk?.iterations,
1100
1211
  executionTimeMs: metricsChunk?.executionTimeMs,
1212
+ executionId,
1101
1213
  raw
1102
1214
  };
1103
1215
  }
@@ -1106,13 +1218,18 @@ async function invokeAgent(cfg, params) {
1106
1218
  return {
1107
1219
  content: responseChunk.content,
1108
1220
  contentSource: "response",
1109
- sessionId: mintedSessionId ?? responseChunk.sessionId ?? metadataSessionId ?? params.sessionId,
1221
+ sessionId,
1110
1222
  model: metricsChunk?.model,
1111
1223
  usage: metricsChunk?.usage,
1112
1224
  sources: responseChunk.sources,
1113
1225
  blocks: responseChunk.blocks,
1226
+ gated: responseChunk.gated,
1114
1227
  iterations: metricsChunk?.iterations,
1115
1228
  executionTimeMs: metricsChunk?.executionTimeMs,
1229
+ executionId,
1230
+ outcome: doneChunk?.outcome,
1231
+ truncated: doneChunk?.truncated,
1232
+ finishReason: doneChunk?.finishReason,
1116
1233
  raw
1117
1234
  };
1118
1235
  }
@@ -1719,6 +1836,7 @@ function signRequest(payload, secretKey, timestamp) {
1719
1836
  ZihinAgentError,
1720
1837
  classifyStreamErrorKind,
1721
1838
  createClient,
1839
+ errorFromDoneChunk,
1722
1840
  errorFromStreamChunk,
1723
1841
  isCommercialBlock,
1724
1842
  sanitizeApiError,
package/dist/index.mjs CHANGED
@@ -13,6 +13,12 @@ function utf8Len(s) {
13
13
  function rec(x) {
14
14
  return x && typeof x === "object" ? x : {};
15
15
  }
16
+ function safeId(x) {
17
+ return typeof x === "string" && /^[\w.:-]{1,128}$/.test(x) ? x : void 0;
18
+ }
19
+ function safeLabel(x) {
20
+ return typeof x === "string" && /^[A-Za-z0-9_]{1,64}$/.test(x) ? x : void 0;
21
+ }
16
22
  function str(x) {
17
23
  return typeof x === "string" ? x : void 0;
18
24
  }
@@ -38,8 +44,13 @@ var RUNNING_STATUSES = /* @__PURE__ */ new Set([
38
44
  "waiting",
39
45
  "retrying",
40
46
  "scheduled",
41
- "awaiting_approval"
47
+ "awaiting_approval",
42
48
  // HITL: turno pausado esperando decisão humana não terminou
49
+ // Estados VIVOS reais do backend (zihin-agent-builder `TaskService.LIVE_STATUSES` e
50
+ // `agent_executions.status`): sem eles, uma task/execução pausada ou aguardando input
51
+ // era lida como TERMINAL e o polling parava com o trabalho ainda em andamento.
52
+ "paused",
53
+ "awaiting_input"
43
54
  ]);
44
55
  function isTerminalStatus(status) {
45
56
  return !RUNNING_STATUSES.has(status.toLowerCase().replace(/-/g, "_"));
@@ -234,6 +245,9 @@ var ERROR_CODE_MAP = {
234
245
  INSUFFICIENT_CREDITS: "Insufficient credits. Please check your account balance.",
235
246
  CONTEXT_LENGTH_EXCEEDED: "The input exceeds the model context length limit.",
236
247
  AGENT_NOT_FOUND: "Agent not found or not active for this tenant.",
248
+ // Nome novo do `AGENT_NOT_FOUND` (zihin-agent-builder#561, fatia B). `AGENT_ERROR` virou
249
+ // `INTERNAL_ERROR`, que já está acima.
250
+ NOT_FOUND: "Resource not found \u2014 check the ID (agent, trigger or execution) and that it belongs to this workspace.",
237
251
  // Bloqueios comerciais. As mensagens do BE vêm em português e estes shells são em inglês;
238
252
  // além disso cada caso tem uma ação DIFERENTE, e o texto precisa dizer qual.
239
253
  TRIAL_EXPIRED: "Your Zihin trial has ended. Upgrade the plan to keep invoking agents \u2014 the API key is fine.",
@@ -304,6 +318,8 @@ var ZihinAgentError = class _ZihinAgentError extends Error {
304
318
  this.code = opts.code;
305
319
  this.requestId = opts.requestId;
306
320
  this.executionId = opts.executionId;
321
+ this.sessionId = opts.sessionId;
322
+ this.timeoutClock = opts.timeoutClock;
307
323
  this.attempts = opts.attempts;
308
324
  this.endpoint = opts.endpoint;
309
325
  this.retryable = opts.retryable ?? (kind === "rate_limit" || kind === "server" || kind === "network");
@@ -338,7 +354,7 @@ function commercialKind(body) {
338
354
  }
339
355
  function extractRequestId(body) {
340
356
  const b = rec(body);
341
- return str(b.request_id) ?? str(b.requestId) ?? str(rec(b.error).request_id) ?? str(rec(b.meta).request_id);
357
+ return safeId(b.request_id) ?? safeId(b.requestId) ?? safeId(rec(b.error).request_id) ?? safeId(rec(b.meta).request_id);
342
358
  }
343
359
  function errorFromResponse(status, body, meta = {}) {
344
360
  const sanitized = sanitizeApiError(body);
@@ -348,7 +364,7 @@ function errorFromResponse(status, body, meta = {}) {
348
364
  status,
349
365
  code,
350
366
  requestId: extractRequestId(body),
351
- executionId: str(rec(body).execution_id),
367
+ executionId: safeId(rec(body).execution_id),
352
368
  ...meta
353
369
  };
354
370
  const commercial = commercialKind(body);
@@ -359,6 +375,12 @@ function errorFromResponse(status, body, meta = {}) {
359
375
  });
360
376
  }
361
377
  if (status === 401) return new ZihinAgentError("auth", message || "Authentication failed.", base);
378
+ if (status === 403 && str(rec(rec(rec(body).error).details).reason) === "AGENT_ACCESS_DENIED")
379
+ return new ZihinAgentError(
380
+ "not_found",
381
+ "Agent not found, inactive, or not accessible with this API key \u2014 check the agent ID and that the key belongs to the same Zihin workspace.",
382
+ { ...base, retryable: false }
383
+ );
362
384
  if (status === 403)
363
385
  return new ZihinAgentError(
364
386
  "auth",
@@ -408,7 +430,7 @@ var OPERATION_TIMEOUT_MS = Object.freeze({
408
430
  dispatch: 26e4,
409
431
  short: 35e3
410
432
  });
411
- var DEADLINE_GRACE_MS = 1e4;
433
+ var DEADLINE_GRACE_MS = 3e4;
412
434
  var RETRY_OVERHEAD_MS = 6e4;
413
435
  var MIN_TIMEOUT_MS = 1e3;
414
436
  var MAX_TIMEOUT_MS = 9e5;
@@ -860,13 +882,13 @@ function mapEvent(e) {
860
882
  case "metadata":
861
883
  return {
862
884
  type: "metadata",
863
- requestId: str(d.request_id),
885
+ requestId: safeId(d.request_id),
864
886
  agentId: str(d.agent_id),
865
- sessionId: str(d.session_id),
887
+ sessionId: safeId(d.session_id),
866
888
  data: e.data
867
889
  };
868
890
  case "status":
869
- return { type: "status", phase: str(d.phase), data: e.data };
891
+ return { type: "status", phase: str(d.phase), blocked: safeLabel(d.blocked), data: e.data };
870
892
  case "attachments":
871
893
  return { type: "attachments", data: e.data };
872
894
  case "phase":
@@ -888,16 +910,24 @@ function mapEvent(e) {
888
910
  case "warning":
889
911
  return { type: "warning", message: str(d.message), data: e.data };
890
912
  case "session":
891
- return { type: "session", sessionId: str(d.session_id), data: e.data };
913
+ return { type: "session", sessionId: safeId(d.session_id), data: e.data };
892
914
  case "done":
893
- return { type: "done", data: e.data };
915
+ return {
916
+ type: "done",
917
+ outcome: safeLabel(d.outcome),
918
+ timedOut: typeof d.timed_out === "boolean" ? d.timed_out : void 0,
919
+ timeoutClock: safeLabel(d.timeout_clock),
920
+ truncated: typeof d.truncated === "boolean" ? d.truncated : void 0,
921
+ finishReason: str(d.finish_reason),
922
+ data: e.data
923
+ };
894
924
  case "error": {
895
925
  const err = rec(d.error);
896
926
  return {
897
927
  type: "error",
898
- code: str(err.code),
928
+ code: safeLabel(err.code),
899
929
  message: str(err.message),
900
- requestId: str(err.request_id),
930
+ requestId: safeId(err.request_id),
901
931
  timedOut: typeof err.timed_out === "boolean" ? err.timed_out : void 0,
902
932
  data: e.data
903
933
  };
@@ -906,7 +936,8 @@ function mapEvent(e) {
906
936
  return {
907
937
  type: "response",
908
938
  content: str(d.content) ?? "",
909
- sessionId: str(d.session_id),
939
+ sessionId: safeId(d.session_id),
940
+ gated: d.gated === true ? true : void 0,
910
941
  sources: Array.isArray(d.sources) ? d.sources : void 0,
911
942
  blocks: Array.isArray(d.blocks) ? d.blocks : void 0,
912
943
  data: e.data
@@ -919,6 +950,7 @@ function mapEvent(e) {
919
950
  model: str(modelConfig.model) ?? str(d.model),
920
951
  iterations: num(d.iterations),
921
952
  executionTimeMs: num(d.execution_time_ms),
953
+ executionId: safeId(d.execution_id),
922
954
  data: e.data
923
955
  };
924
956
  }
@@ -1031,6 +1063,77 @@ function errorFromStreamChunk(chunk) {
1031
1063
  retryable: false
1032
1064
  });
1033
1065
  }
1066
+ var FAILED_OUTCOMES = {
1067
+ ERROR: { kind: "server", message: "The agent turn failed on the server; its text is the engine failure notice, not an answer." },
1068
+ // `server`, não `aborted`: `aborted` é o cancelamento do PRÓPRIO chamador, que `onError` manda
1069
+ // filtrar — um cancelamento feito pelo operador sumiria junto.
1070
+ CANCELLED: { kind: "server", message: "The agent turn was cancelled on the server (by an operator or a platform guard)." },
1071
+ // Modo processo drenando/encerrando; o motor retoma depois. Nenhum canal atual o produz.
1072
+ SUSPENDED: { kind: "blocked", message: "The agent turn was suspended by the server and did not answer; retry later." },
1073
+ BUDGET_EXCEEDED: { kind: "blocked", message: "The agent's spending cap was reached; raise the agent budget to keep invoking it." },
1074
+ QUOTA_EXCEEDED: { kind: "quota_exceeded", message: "Your Zihin token quota is exhausted. Upgrade the plan or wait for the next cycle." },
1075
+ SESSION_BUSY: {
1076
+ kind: "blocked",
1077
+ message: "Another turn is still running in this session; retry after it finishes.",
1078
+ retryable: true
1079
+ },
1080
+ // Hoje a rota manda CONFLICT como `event: error` (não chega ao `done`); mapeado por defesa.
1081
+ CONFLICT: {
1082
+ kind: "blocked",
1083
+ message: "The declared consumer identity does not match the one already bound to this session."
1084
+ }
1085
+ };
1086
+ var PAUSED_BLOCKS = /* @__PURE__ */ new Set(["agent_paused", "agent_archived"]);
1087
+ var ERROR_BLOCKS = {
1088
+ ownership_denied: {
1089
+ kind: "not_found",
1090
+ message: "Session not found for this API key \u2014 start a new conversation (omit sessionId) instead of reusing this one."
1091
+ },
1092
+ session_load_failed: { kind: "blocked", message: "The server could not load this conversation right now; retry shortly.", retryable: true },
1093
+ approval_resolve_failed: { kind: "blocked", message: "The server could not route the required approval right now; retry shortly.", retryable: true },
1094
+ approval_no_approver: { kind: "blocked", message: "This action requires approval, but the approval policy has no approver configured." },
1095
+ blocked_schedule: { kind: "blocked", message: "The agent is outside its allowed schedule (security policy)." },
1096
+ owner_inactive: { kind: "blocked", message: "The agent's owner is inactive; the agent cannot be invoked." }
1097
+ };
1098
+ function own(table, key) {
1099
+ return key !== void 0 && Object.prototype.hasOwnProperty.call(table, key) ? table[key] : void 0;
1100
+ }
1101
+ function errorFromDoneChunk(chunk, ctx = {}) {
1102
+ const meta = { executionId: ctx.executionId, sessionId: ctx.sessionId };
1103
+ const blockedReason = safeLabel(ctx.blocked);
1104
+ if (chunk.outcome === "TIMEOUT" || chunk.timedOut === true) {
1105
+ const clock = chunk.timeoutClock;
1106
+ const what = clock === "turn" ? "the turn deadline" : clock === "first_signal" ? "the model provider did not start responding in time" : clock === "idle" ? "the model provider stream went silent" : clock === "call" ? "the model call took too long" : "a server-side clock";
1107
+ return new ZihinAgentError("timeout", `The agent turn timed out (${what}).`, {
1108
+ ...meta,
1109
+ code: "TURN_TIMEOUT",
1110
+ retryable: false,
1111
+ timeoutClock: clock
1112
+ });
1113
+ }
1114
+ if (chunk.outcome === "PAUSED" && blockedReason && PAUSED_BLOCKS.has(blockedReason)) {
1115
+ return new ZihinAgentError("blocked", "The agent is paused or archived and did not answer.", {
1116
+ ...meta,
1117
+ code: "PAUSED",
1118
+ retryable: false
1119
+ });
1120
+ }
1121
+ if (chunk.outcome === "ERROR" && blockedReason) {
1122
+ const gate = own(ERROR_BLOCKS, blockedReason);
1123
+ return new ZihinAgentError(gate?.kind ?? "blocked", gate?.message ?? `The agent turn was blocked by the server (${blockedReason}).`, {
1124
+ ...meta,
1125
+ code: blockedReason.toUpperCase(),
1126
+ retryable: gate?.retryable ?? false
1127
+ });
1128
+ }
1129
+ const failed = own(FAILED_OUTCOMES, chunk.outcome);
1130
+ if (!failed) return void 0;
1131
+ return new ZihinAgentError(failed.kind, failed.message, {
1132
+ ...meta,
1133
+ code: chunk.outcome,
1134
+ retryable: failed.retryable ?? false
1135
+ });
1136
+ }
1034
1137
  async function invokeAgent(cfg, params) {
1035
1138
  const raw = [];
1036
1139
  const keepRaw = params.includeRaw === true;
@@ -1038,7 +1141,8 @@ async function invokeAgent(cfg, params) {
1038
1141
  let metricsChunk;
1039
1142
  let metadataSessionId;
1040
1143
  let mintedSessionId;
1041
- let cleanEnd = false;
1144
+ let doneChunk;
1145
+ let blocked;
1042
1146
  let tokenBuffer = "";
1043
1147
  for await (const chunk of streamAgent(cfg, params)) {
1044
1148
  if (keepRaw) raw.push(chunk);
@@ -1050,8 +1154,14 @@ async function invokeAgent(cfg, params) {
1050
1154
  else if (chunk.type === "token") tokenBuffer += chunk.content;
1051
1155
  else if (chunk.type === "metadata" && chunk.sessionId) metadataSessionId = chunk.sessionId;
1052
1156
  else if (chunk.type === "session" && chunk.sessionId) mintedSessionId = chunk.sessionId;
1053
- else if (chunk.type === "done") cleanEnd = true;
1054
- }
1157
+ else if (chunk.type === "done") doneChunk = chunk;
1158
+ else if (chunk.type === "status" && chunk.blocked) blocked = chunk.blocked;
1159
+ }
1160
+ const sessionId = mintedSessionId ?? responseChunk?.sessionId ?? metadataSessionId ?? params.sessionId;
1161
+ const executionId = metricsChunk?.executionId;
1162
+ const outcomeError = doneChunk && errorFromDoneChunk(doneChunk, { executionId, sessionId, blocked });
1163
+ if (outcomeError) throw outcomeError;
1164
+ const cleanEnd = doneChunk !== void 0;
1055
1165
  if (!responseChunk) {
1056
1166
  if (cleanEnd) {
1057
1167
  throw new ZihinAgentError("protocol", "Stream ended (done) without a `response` event.");
@@ -1060,11 +1170,12 @@ async function invokeAgent(cfg, params) {
1060
1170
  return {
1061
1171
  content: tokenBuffer,
1062
1172
  contentSource: "tokens",
1063
- sessionId: mintedSessionId ?? metadataSessionId ?? params.sessionId,
1173
+ sessionId,
1064
1174
  usage: metricsChunk?.usage,
1065
1175
  model: metricsChunk?.model,
1066
1176
  iterations: metricsChunk?.iterations,
1067
1177
  executionTimeMs: metricsChunk?.executionTimeMs,
1178
+ executionId,
1068
1179
  raw
1069
1180
  };
1070
1181
  }
@@ -1073,13 +1184,18 @@ async function invokeAgent(cfg, params) {
1073
1184
  return {
1074
1185
  content: responseChunk.content,
1075
1186
  contentSource: "response",
1076
- sessionId: mintedSessionId ?? responseChunk.sessionId ?? metadataSessionId ?? params.sessionId,
1187
+ sessionId,
1077
1188
  model: metricsChunk?.model,
1078
1189
  usage: metricsChunk?.usage,
1079
1190
  sources: responseChunk.sources,
1080
1191
  blocks: responseChunk.blocks,
1192
+ gated: responseChunk.gated,
1081
1193
  iterations: metricsChunk?.iterations,
1082
1194
  executionTimeMs: metricsChunk?.executionTimeMs,
1195
+ executionId,
1196
+ outcome: doneChunk?.outcome,
1197
+ truncated: doneChunk?.truncated,
1198
+ finishReason: doneChunk?.finishReason,
1083
1199
  raw
1084
1200
  };
1085
1201
  }
@@ -1685,6 +1801,7 @@ export {
1685
1801
  ZihinAgentError,
1686
1802
  classifyStreamErrorKind,
1687
1803
  createClient,
1804
+ errorFromDoneChunk,
1688
1805
  errorFromStreamChunk,
1689
1806
  isCommercialBlock,
1690
1807
  sanitizeApiError,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zihin/agent-client",
3
- "version": "0.4.1",
3
+ "version": "0.5.0",
4
4
  "description": "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.",
5
5
  "keywords": [
6
6
  "zihin",