@zihin/agent-client 0.4.0 → 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 +21 -0
- package/README.md +37 -3
- package/dist/index.d.mts +140 -10
- package/dist/index.d.ts +140 -10
- package/dist/index.js +155 -24
- package/dist/index.mjs +152 -24
- package/package.json +1 -1
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
|
@@ -159,8 +159,35 @@ await zihin.cancelTask({ taskId: task.id, reason: 'mudou o plano' }); // 409 se
|
|
|
159
159
|
|
|
160
160
|
Um `content` vazio com `contentSource: 'response'` é decisão do BE, não lacuna — o núcleo **não**
|
|
161
161
|
preenche com tokens. E quando o BE fecha o stream com `event: error` (turno morto no deadline, por
|
|
162
|
-
exemplo), o núcleo **lança** (`kind: 'timeout'
|
|
163
|
-
|
|
162
|
+
exemplo), o núcleo **lança** (`kind: 'timeout'`, com `code` e `requestId`) em vez de devolver o
|
|
163
|
+
parcial. Se foi timeout ou não vem do campo **explícito `error.timed_out`** do evento (BE desde
|
|
164
|
+
26/09/2026): quando presente ele é autoritativo — `true` é timeout seja qual for o `code`, `false`
|
|
165
|
+
é `server` mesmo com "TIMEOUT" no nome. Sem o campo (backend antigo), cai no fallback por nome
|
|
166
|
+
(`/TIMEOUT/i` no `code`), que será removido numa major futura. Não ramifique no **nome** do código
|
|
167
|
+
de timeout: o BE vai renomeá-los (zihin-agent-builder#561); use `kind`.
|
|
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.
|
|
164
191
|
|
|
165
192
|
## Dropdown de agentes
|
|
166
193
|
|
|
@@ -199,6 +226,10 @@ status, virariam "troque a key" ou "tente de novo". Têm `kind` próprio e **nun
|
|
|
199
226
|
(`kind: 'auth'`) significa que o **papel** da key não permite a ação — e o papel exigido varia por
|
|
200
227
|
rota (editor cria trigger, mas não delega task), então a mensagem não enumera papéis.
|
|
201
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
|
+
|
|
202
233
|
`ZihinAgentError.executionId` vem preenchido quando o BE o entrega (sucesso e erro) — é o handle para
|
|
203
234
|
correlacionar com o console.
|
|
204
235
|
|
|
@@ -209,7 +240,10 @@ correlacionar com o console.
|
|
|
209
240
|
ACIMA do orçamento do BE para aquela rota (SSE 180s, turno async 240s, timeout global 30s), para
|
|
210
241
|
que o desfecho do servidor chegue como erro estruturado em vez de abort cego do cliente.
|
|
211
242
|
`timeoutMs` na config sobrepõe todos; `options.timeoutMs` por chamada vai ao BE como
|
|
212
|
-
`timeout_ms
|
|
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
|
|
213
247
|
da tentativa seguinte — o que fica limitado é o tempo gasto com retries.
|
|
214
248
|
- **Retry sem duplicar efeito** — GET (idempotente) retenta em rede/429/5xx; POST com efeito
|
|
215
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
|
-
|
|
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
|
-
/**
|
|
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';
|
|
@@ -223,16 +277,21 @@ type AgentStreamChunk = {
|
|
|
223
277
|
data: unknown;
|
|
224
278
|
}
|
|
225
279
|
/**
|
|
226
|
-
* O BE encerra o stream com `event: error` + `{ error: { type, code, message, request_id
|
|
227
|
-
* (`utils/sse.js
|
|
228
|
-
* `unknown` e o `invokeAgent` ignorava: se já tinha chegado token, devolvia a
|
|
229
|
-
* crua como SUCESSO.
|
|
280
|
+
* O BE encerra o stream com `event: error` + `{ error: { type, code, message, request_id,
|
|
281
|
+
* timed_out? } }` (`utils/sse.js`) — inclusive quando o TURNO morre no deadline. Antes isto
|
|
282
|
+
* caía em `unknown` e o `invokeAgent` ignorava: se já tinha chegado token, devolvia a
|
|
283
|
+
* deliberação crua como SUCESSO.
|
|
284
|
+
*
|
|
285
|
+
* `timedOut` espelha `error.timed_out` (BE desde 26/09/2026, zihin-agent-builder#605): campo
|
|
286
|
+
* explícito que diz se o turno morreu por timeout, independente do NOME do código — os códigos
|
|
287
|
+
* vão ser renomeados (zihin-agent-builder#561). `undefined` = backend antigo, sem o campo.
|
|
230
288
|
*/
|
|
231
289
|
| {
|
|
232
290
|
type: 'error';
|
|
233
291
|
code?: string;
|
|
234
292
|
message?: string;
|
|
235
293
|
requestId?: string;
|
|
294
|
+
timedOut?: boolean;
|
|
236
295
|
data: unknown;
|
|
237
296
|
}
|
|
238
297
|
/**
|
|
@@ -245,9 +304,20 @@ type AgentStreamChunk = {
|
|
|
245
304
|
sessionId?: string;
|
|
246
305
|
data: unknown;
|
|
247
306
|
}
|
|
248
|
-
/**
|
|
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
|
+
*/
|
|
249
314
|
| {
|
|
250
315
|
type: 'done';
|
|
316
|
+
outcome?: AgentOutcome;
|
|
317
|
+
timedOut?: boolean;
|
|
318
|
+
timeoutClock?: TimeoutClock;
|
|
319
|
+
truncated?: boolean;
|
|
320
|
+
finishReason?: string;
|
|
251
321
|
data: unknown;
|
|
252
322
|
} | {
|
|
253
323
|
type: 'response';
|
|
@@ -258,6 +328,8 @@ type AgentStreamChunk = {
|
|
|
258
328
|
type: string;
|
|
259
329
|
[k: string]: unknown;
|
|
260
330
|
}>;
|
|
331
|
+
/** Resposta ENLATADA de um gate (handoff humano, denylist, sessão suspensa) — não do agente. */
|
|
332
|
+
gated?: boolean;
|
|
261
333
|
data: unknown;
|
|
262
334
|
} | {
|
|
263
335
|
type: 'metrics';
|
|
@@ -265,6 +337,7 @@ type AgentStreamChunk = {
|
|
|
265
337
|
model?: string;
|
|
266
338
|
iterations?: number;
|
|
267
339
|
executionTimeMs?: number;
|
|
340
|
+
executionId?: string;
|
|
268
341
|
data: unknown;
|
|
269
342
|
} | {
|
|
270
343
|
type: 'unknown';
|
|
@@ -584,8 +657,13 @@ type TimeoutOperation = keyof typeof OPERATION_TIMEOUT_MS;
|
|
|
584
657
|
* Folga somada ao `options.timeoutMs` do chamador para formar o deadline LOCAL. O mesmo número vai
|
|
585
658
|
* ao BE como `options.timeout_ms` (o teto que ele deve respeitar); sem folga, os dois relógios
|
|
586
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.
|
|
587
665
|
*/
|
|
588
|
-
declare const DEADLINE_GRACE_MS =
|
|
666
|
+
declare const DEADLINE_GRACE_MS = 30000;
|
|
589
667
|
|
|
590
668
|
/**
|
|
591
669
|
* Listagem de agentes do tenant (GET /api/v2/agents). Alimenta o dropdown do nó n8n (#17)
|
|
@@ -664,6 +742,58 @@ declare class ZihinAgentClient {
|
|
|
664
742
|
/** Açúcar: cria um cliente a partir da config. */
|
|
665
743
|
declare function createClient(config: ZihinClientConfig): ZihinAgentClient;
|
|
666
744
|
|
|
745
|
+
/**
|
|
746
|
+
* Caminho A — invocação direta de agente (POST /api/v2/agents/:id/stream).
|
|
747
|
+
* `streamAgent` emite deltas ao vivo; `invokeAgent` buffeia o stream e consolida
|
|
748
|
+
* o evento `response` (content/sources/session_id/blocks) com o `metrics` (usage/model).
|
|
749
|
+
*
|
|
750
|
+
* @module invoke
|
|
751
|
+
*/
|
|
752
|
+
|
|
753
|
+
/**
|
|
754
|
+
* Decide se o `event: error` terminal do BE foi um timeout do turno (#75).
|
|
755
|
+
*
|
|
756
|
+
* Fonte primária é o campo EXPLÍCITO `error.timed_out` (BE `utils/sse.js`, em prod desde
|
|
757
|
+
* 26/09/2026 — zihin-agent-builder#605): quando presente, é autoritativo nos dois sentidos —
|
|
758
|
+
* `true` classifica como timeout seja qual for o `code`; `false` classifica como `server` mesmo
|
|
759
|
+
* que o nome do código contenha "TIMEOUT". O BE vai renomear esses códigos
|
|
760
|
+
* (zihin-agent-builder#561) e o nome deixa de ser um sinal confiável.
|
|
761
|
+
*
|
|
762
|
+
* O regex no nome só vale como FALLBACK para backends que ainda não emitem `timed_out`
|
|
763
|
+
* (self-hosted/antigos). Some numa major futura, depois do rename no BE.
|
|
764
|
+
*/
|
|
765
|
+
declare function classifyStreamErrorKind(code: string, timedOut?: boolean): 'timeout' | 'server';
|
|
766
|
+
/**
|
|
767
|
+
* Converte o chunk `type: 'error'` (evento terminal do BE) num `ZihinAgentError` — a MESMA regra
|
|
768
|
+
* que o `invokeAgent` aplica, exportada para quem consome `streamAgent` direto (o adapter do
|
|
769
|
+
* Paperclip, por exemplo) não reimplementar a classificação por conta e divergir dela.
|
|
770
|
+
*/
|
|
771
|
+
declare function errorFromStreamChunk(chunk: Extract<AgentStreamChunk, {
|
|
772
|
+
type: 'error';
|
|
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;
|
|
796
|
+
|
|
667
797
|
/**
|
|
668
798
|
* Security utilities — portado de n8n-nodes-zihin/src/utils/security.ts (zero-dep).
|
|
669
799
|
* SSRF prevention, payload limits, error sanitization, API-key format.
|
|
@@ -786,4 +916,4 @@ declare function signRequest(payload: string, secretKey: string, timestamp?: num
|
|
|
786
916
|
*/
|
|
787
917
|
declare const MAX_TASK_PAYLOAD_BYTES: number;
|
|
788
918
|
|
|
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 };
|
|
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
|
-
|
|
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
|
-
/**
|
|
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';
|
|
@@ -223,16 +277,21 @@ type AgentStreamChunk = {
|
|
|
223
277
|
data: unknown;
|
|
224
278
|
}
|
|
225
279
|
/**
|
|
226
|
-
* O BE encerra o stream com `event: error` + `{ error: { type, code, message, request_id
|
|
227
|
-
* (`utils/sse.js
|
|
228
|
-
* `unknown` e o `invokeAgent` ignorava: se já tinha chegado token, devolvia a
|
|
229
|
-
* crua como SUCESSO.
|
|
280
|
+
* O BE encerra o stream com `event: error` + `{ error: { type, code, message, request_id,
|
|
281
|
+
* timed_out? } }` (`utils/sse.js`) — inclusive quando o TURNO morre no deadline. Antes isto
|
|
282
|
+
* caía em `unknown` e o `invokeAgent` ignorava: se já tinha chegado token, devolvia a
|
|
283
|
+
* deliberação crua como SUCESSO.
|
|
284
|
+
*
|
|
285
|
+
* `timedOut` espelha `error.timed_out` (BE desde 26/09/2026, zihin-agent-builder#605): campo
|
|
286
|
+
* explícito que diz se o turno morreu por timeout, independente do NOME do código — os códigos
|
|
287
|
+
* vão ser renomeados (zihin-agent-builder#561). `undefined` = backend antigo, sem o campo.
|
|
230
288
|
*/
|
|
231
289
|
| {
|
|
232
290
|
type: 'error';
|
|
233
291
|
code?: string;
|
|
234
292
|
message?: string;
|
|
235
293
|
requestId?: string;
|
|
294
|
+
timedOut?: boolean;
|
|
236
295
|
data: unknown;
|
|
237
296
|
}
|
|
238
297
|
/**
|
|
@@ -245,9 +304,20 @@ type AgentStreamChunk = {
|
|
|
245
304
|
sessionId?: string;
|
|
246
305
|
data: unknown;
|
|
247
306
|
}
|
|
248
|
-
/**
|
|
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
|
+
*/
|
|
249
314
|
| {
|
|
250
315
|
type: 'done';
|
|
316
|
+
outcome?: AgentOutcome;
|
|
317
|
+
timedOut?: boolean;
|
|
318
|
+
timeoutClock?: TimeoutClock;
|
|
319
|
+
truncated?: boolean;
|
|
320
|
+
finishReason?: string;
|
|
251
321
|
data: unknown;
|
|
252
322
|
} | {
|
|
253
323
|
type: 'response';
|
|
@@ -258,6 +328,8 @@ type AgentStreamChunk = {
|
|
|
258
328
|
type: string;
|
|
259
329
|
[k: string]: unknown;
|
|
260
330
|
}>;
|
|
331
|
+
/** Resposta ENLATADA de um gate (handoff humano, denylist, sessão suspensa) — não do agente. */
|
|
332
|
+
gated?: boolean;
|
|
261
333
|
data: unknown;
|
|
262
334
|
} | {
|
|
263
335
|
type: 'metrics';
|
|
@@ -265,6 +337,7 @@ type AgentStreamChunk = {
|
|
|
265
337
|
model?: string;
|
|
266
338
|
iterations?: number;
|
|
267
339
|
executionTimeMs?: number;
|
|
340
|
+
executionId?: string;
|
|
268
341
|
data: unknown;
|
|
269
342
|
} | {
|
|
270
343
|
type: 'unknown';
|
|
@@ -584,8 +657,13 @@ type TimeoutOperation = keyof typeof OPERATION_TIMEOUT_MS;
|
|
|
584
657
|
* Folga somada ao `options.timeoutMs` do chamador para formar o deadline LOCAL. O mesmo número vai
|
|
585
658
|
* ao BE como `options.timeout_ms` (o teto que ele deve respeitar); sem folga, os dois relógios
|
|
586
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.
|
|
587
665
|
*/
|
|
588
|
-
declare const DEADLINE_GRACE_MS =
|
|
666
|
+
declare const DEADLINE_GRACE_MS = 30000;
|
|
589
667
|
|
|
590
668
|
/**
|
|
591
669
|
* Listagem de agentes do tenant (GET /api/v2/agents). Alimenta o dropdown do nó n8n (#17)
|
|
@@ -664,6 +742,58 @@ declare class ZihinAgentClient {
|
|
|
664
742
|
/** Açúcar: cria um cliente a partir da config. */
|
|
665
743
|
declare function createClient(config: ZihinClientConfig): ZihinAgentClient;
|
|
666
744
|
|
|
745
|
+
/**
|
|
746
|
+
* Caminho A — invocação direta de agente (POST /api/v2/agents/:id/stream).
|
|
747
|
+
* `streamAgent` emite deltas ao vivo; `invokeAgent` buffeia o stream e consolida
|
|
748
|
+
* o evento `response` (content/sources/session_id/blocks) com o `metrics` (usage/model).
|
|
749
|
+
*
|
|
750
|
+
* @module invoke
|
|
751
|
+
*/
|
|
752
|
+
|
|
753
|
+
/**
|
|
754
|
+
* Decide se o `event: error` terminal do BE foi um timeout do turno (#75).
|
|
755
|
+
*
|
|
756
|
+
* Fonte primária é o campo EXPLÍCITO `error.timed_out` (BE `utils/sse.js`, em prod desde
|
|
757
|
+
* 26/09/2026 — zihin-agent-builder#605): quando presente, é autoritativo nos dois sentidos —
|
|
758
|
+
* `true` classifica como timeout seja qual for o `code`; `false` classifica como `server` mesmo
|
|
759
|
+
* que o nome do código contenha "TIMEOUT". O BE vai renomear esses códigos
|
|
760
|
+
* (zihin-agent-builder#561) e o nome deixa de ser um sinal confiável.
|
|
761
|
+
*
|
|
762
|
+
* O regex no nome só vale como FALLBACK para backends que ainda não emitem `timed_out`
|
|
763
|
+
* (self-hosted/antigos). Some numa major futura, depois do rename no BE.
|
|
764
|
+
*/
|
|
765
|
+
declare function classifyStreamErrorKind(code: string, timedOut?: boolean): 'timeout' | 'server';
|
|
766
|
+
/**
|
|
767
|
+
* Converte o chunk `type: 'error'` (evento terminal do BE) num `ZihinAgentError` — a MESMA regra
|
|
768
|
+
* que o `invokeAgent` aplica, exportada para quem consome `streamAgent` direto (o adapter do
|
|
769
|
+
* Paperclip, por exemplo) não reimplementar a classificação por conta e divergir dela.
|
|
770
|
+
*/
|
|
771
|
+
declare function errorFromStreamChunk(chunk: Extract<AgentStreamChunk, {
|
|
772
|
+
type: 'error';
|
|
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;
|
|
796
|
+
|
|
667
797
|
/**
|
|
668
798
|
* Security utilities — portado de n8n-nodes-zihin/src/utils/security.ts (zero-dep).
|
|
669
799
|
* SSRF prevention, payload limits, error sanitization, API-key format.
|
|
@@ -786,4 +916,4 @@ declare function signRequest(payload: string, secretKey: string, timestamp?: num
|
|
|
786
916
|
*/
|
|
787
917
|
declare const MAX_TASK_PAYLOAD_BYTES: number;
|
|
788
918
|
|
|
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 };
|
|
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
|
@@ -26,7 +26,10 @@ __export(index_exports, {
|
|
|
26
26
|
OPERATION_TIMEOUT_MS: () => OPERATION_TIMEOUT_MS,
|
|
27
27
|
ZihinAgentClient: () => ZihinAgentClient,
|
|
28
28
|
ZihinAgentError: () => ZihinAgentError,
|
|
29
|
+
classifyStreamErrorKind: () => classifyStreamErrorKind,
|
|
29
30
|
createClient: () => createClient,
|
|
31
|
+
errorFromDoneChunk: () => errorFromDoneChunk,
|
|
32
|
+
errorFromStreamChunk: () => errorFromStreamChunk,
|
|
30
33
|
isCommercialBlock: () => isCommercialBlock,
|
|
31
34
|
sanitizeApiError: () => sanitizeApiError,
|
|
32
35
|
signRequest: () => signRequest,
|
|
@@ -44,6 +47,12 @@ function utf8Len(s) {
|
|
|
44
47
|
function rec(x) {
|
|
45
48
|
return x && typeof x === "object" ? x : {};
|
|
46
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
|
+
}
|
|
47
56
|
function str(x) {
|
|
48
57
|
return typeof x === "string" ? x : void 0;
|
|
49
58
|
}
|
|
@@ -69,8 +78,13 @@ var RUNNING_STATUSES = /* @__PURE__ */ new Set([
|
|
|
69
78
|
"waiting",
|
|
70
79
|
"retrying",
|
|
71
80
|
"scheduled",
|
|
72
|
-
"awaiting_approval"
|
|
81
|
+
"awaiting_approval",
|
|
73
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"
|
|
74
88
|
]);
|
|
75
89
|
function isTerminalStatus(status) {
|
|
76
90
|
return !RUNNING_STATUSES.has(status.toLowerCase().replace(/-/g, "_"));
|
|
@@ -265,6 +279,9 @@ var ERROR_CODE_MAP = {
|
|
|
265
279
|
INSUFFICIENT_CREDITS: "Insufficient credits. Please check your account balance.",
|
|
266
280
|
CONTEXT_LENGTH_EXCEEDED: "The input exceeds the model context length limit.",
|
|
267
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.",
|
|
268
285
|
// Bloqueios comerciais. As mensagens do BE vêm em português e estes shells são em inglês;
|
|
269
286
|
// além disso cada caso tem uma ação DIFERENTE, e o texto precisa dizer qual.
|
|
270
287
|
TRIAL_EXPIRED: "Your Zihin trial has ended. Upgrade the plan to keep invoking agents \u2014 the API key is fine.",
|
|
@@ -335,6 +352,8 @@ var ZihinAgentError = class _ZihinAgentError extends Error {
|
|
|
335
352
|
this.code = opts.code;
|
|
336
353
|
this.requestId = opts.requestId;
|
|
337
354
|
this.executionId = opts.executionId;
|
|
355
|
+
this.sessionId = opts.sessionId;
|
|
356
|
+
this.timeoutClock = opts.timeoutClock;
|
|
338
357
|
this.attempts = opts.attempts;
|
|
339
358
|
this.endpoint = opts.endpoint;
|
|
340
359
|
this.retryable = opts.retryable ?? (kind === "rate_limit" || kind === "server" || kind === "network");
|
|
@@ -369,7 +388,7 @@ function commercialKind(body) {
|
|
|
369
388
|
}
|
|
370
389
|
function extractRequestId(body) {
|
|
371
390
|
const b = rec(body);
|
|
372
|
-
return
|
|
391
|
+
return safeId(b.request_id) ?? safeId(b.requestId) ?? safeId(rec(b.error).request_id) ?? safeId(rec(b.meta).request_id);
|
|
373
392
|
}
|
|
374
393
|
function errorFromResponse(status, body, meta = {}) {
|
|
375
394
|
const sanitized = sanitizeApiError(body);
|
|
@@ -379,7 +398,7 @@ function errorFromResponse(status, body, meta = {}) {
|
|
|
379
398
|
status,
|
|
380
399
|
code,
|
|
381
400
|
requestId: extractRequestId(body),
|
|
382
|
-
executionId:
|
|
401
|
+
executionId: safeId(rec(body).execution_id),
|
|
383
402
|
...meta
|
|
384
403
|
};
|
|
385
404
|
const commercial = commercialKind(body);
|
|
@@ -390,6 +409,12 @@ function errorFromResponse(status, body, meta = {}) {
|
|
|
390
409
|
});
|
|
391
410
|
}
|
|
392
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
|
+
);
|
|
393
418
|
if (status === 403)
|
|
394
419
|
return new ZihinAgentError(
|
|
395
420
|
"auth",
|
|
@@ -439,7 +464,7 @@ var OPERATION_TIMEOUT_MS = Object.freeze({
|
|
|
439
464
|
dispatch: 26e4,
|
|
440
465
|
short: 35e3
|
|
441
466
|
});
|
|
442
|
-
var DEADLINE_GRACE_MS =
|
|
467
|
+
var DEADLINE_GRACE_MS = 3e4;
|
|
443
468
|
var RETRY_OVERHEAD_MS = 6e4;
|
|
444
469
|
var MIN_TIMEOUT_MS = 1e3;
|
|
445
470
|
var MAX_TIMEOUT_MS = 9e5;
|
|
@@ -891,13 +916,13 @@ function mapEvent(e) {
|
|
|
891
916
|
case "metadata":
|
|
892
917
|
return {
|
|
893
918
|
type: "metadata",
|
|
894
|
-
requestId:
|
|
919
|
+
requestId: safeId(d.request_id),
|
|
895
920
|
agentId: str(d.agent_id),
|
|
896
|
-
sessionId:
|
|
921
|
+
sessionId: safeId(d.session_id),
|
|
897
922
|
data: e.data
|
|
898
923
|
};
|
|
899
924
|
case "status":
|
|
900
|
-
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 };
|
|
901
926
|
case "attachments":
|
|
902
927
|
return { type: "attachments", data: e.data };
|
|
903
928
|
case "phase":
|
|
@@ -919,16 +944,25 @@ function mapEvent(e) {
|
|
|
919
944
|
case "warning":
|
|
920
945
|
return { type: "warning", message: str(d.message), data: e.data };
|
|
921
946
|
case "session":
|
|
922
|
-
return { type: "session", sessionId:
|
|
947
|
+
return { type: "session", sessionId: safeId(d.session_id), data: e.data };
|
|
923
948
|
case "done":
|
|
924
|
-
return {
|
|
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
|
+
};
|
|
925
958
|
case "error": {
|
|
926
959
|
const err = rec(d.error);
|
|
927
960
|
return {
|
|
928
961
|
type: "error",
|
|
929
|
-
code:
|
|
962
|
+
code: safeLabel(err.code),
|
|
930
963
|
message: str(err.message),
|
|
931
|
-
requestId:
|
|
964
|
+
requestId: safeId(err.request_id),
|
|
965
|
+
timedOut: typeof err.timed_out === "boolean" ? err.timed_out : void 0,
|
|
932
966
|
data: e.data
|
|
933
967
|
};
|
|
934
968
|
}
|
|
@@ -936,7 +970,8 @@ function mapEvent(e) {
|
|
|
936
970
|
return {
|
|
937
971
|
type: "response",
|
|
938
972
|
content: str(d.content) ?? "",
|
|
939
|
-
sessionId:
|
|
973
|
+
sessionId: safeId(d.session_id),
|
|
974
|
+
gated: d.gated === true ? true : void 0,
|
|
940
975
|
sources: Array.isArray(d.sources) ? d.sources : void 0,
|
|
941
976
|
blocks: Array.isArray(d.blocks) ? d.blocks : void 0,
|
|
942
977
|
data: e.data
|
|
@@ -949,6 +984,7 @@ function mapEvent(e) {
|
|
|
949
984
|
model: str(modelConfig.model) ?? str(d.model),
|
|
950
985
|
iterations: num(d.iterations),
|
|
951
986
|
executionTimeMs: num(d.execution_time_ms),
|
|
987
|
+
executionId: safeId(d.execution_id),
|
|
952
988
|
data: e.data
|
|
953
989
|
};
|
|
954
990
|
}
|
|
@@ -1047,6 +1083,91 @@ async function* streamAgent(cfg, params) {
|
|
|
1047
1083
|
deadline.dispose();
|
|
1048
1084
|
}
|
|
1049
1085
|
}
|
|
1086
|
+
function classifyStreamErrorKind(code, timedOut) {
|
|
1087
|
+
if (timedOut === true) return "timeout";
|
|
1088
|
+
if (timedOut === false) return "server";
|
|
1089
|
+
return /TIMEOUT/i.test(code) ? "timeout" : "server";
|
|
1090
|
+
}
|
|
1091
|
+
function errorFromStreamChunk(chunk) {
|
|
1092
|
+
const code = chunk.code ?? "AGENT_ERROR";
|
|
1093
|
+
const kind = classifyStreamErrorKind(code, chunk.timedOut);
|
|
1094
|
+
return new ZihinAgentError(kind, sanitizeApiError(chunk.message ?? code).message || code, {
|
|
1095
|
+
code,
|
|
1096
|
+
requestId: chunk.requestId,
|
|
1097
|
+
retryable: false
|
|
1098
|
+
});
|
|
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
|
+
}
|
|
1050
1171
|
async function invokeAgent(cfg, params) {
|
|
1051
1172
|
const raw = [];
|
|
1052
1173
|
const keepRaw = params.includeRaw === true;
|
|
@@ -1054,26 +1175,27 @@ async function invokeAgent(cfg, params) {
|
|
|
1054
1175
|
let metricsChunk;
|
|
1055
1176
|
let metadataSessionId;
|
|
1056
1177
|
let mintedSessionId;
|
|
1057
|
-
let
|
|
1178
|
+
let doneChunk;
|
|
1179
|
+
let blocked;
|
|
1058
1180
|
let tokenBuffer = "";
|
|
1059
1181
|
for await (const chunk of streamAgent(cfg, params)) {
|
|
1060
1182
|
if (keepRaw) raw.push(chunk);
|
|
1061
1183
|
if (chunk.type === "error") {
|
|
1062
|
-
|
|
1063
|
-
const kind = /TIMEOUT/i.test(code) ? "timeout" : "server";
|
|
1064
|
-
throw new ZihinAgentError(kind, sanitizeApiError(chunk.message ?? code).message || code, {
|
|
1065
|
-
code,
|
|
1066
|
-
requestId: chunk.requestId,
|
|
1067
|
-
retryable: false
|
|
1068
|
-
});
|
|
1184
|
+
throw errorFromStreamChunk(chunk);
|
|
1069
1185
|
}
|
|
1070
1186
|
if (chunk.type === "response") responseChunk = chunk;
|
|
1071
1187
|
else if (chunk.type === "metrics") metricsChunk = chunk;
|
|
1072
1188
|
else if (chunk.type === "token") tokenBuffer += chunk.content;
|
|
1073
1189
|
else if (chunk.type === "metadata" && chunk.sessionId) metadataSessionId = chunk.sessionId;
|
|
1074
1190
|
else if (chunk.type === "session" && chunk.sessionId) mintedSessionId = chunk.sessionId;
|
|
1075
|
-
else if (chunk.type === "done")
|
|
1076
|
-
|
|
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;
|
|
1077
1199
|
if (!responseChunk) {
|
|
1078
1200
|
if (cleanEnd) {
|
|
1079
1201
|
throw new ZihinAgentError("protocol", "Stream ended (done) without a `response` event.");
|
|
@@ -1082,11 +1204,12 @@ async function invokeAgent(cfg, params) {
|
|
|
1082
1204
|
return {
|
|
1083
1205
|
content: tokenBuffer,
|
|
1084
1206
|
contentSource: "tokens",
|
|
1085
|
-
sessionId
|
|
1207
|
+
sessionId,
|
|
1086
1208
|
usage: metricsChunk?.usage,
|
|
1087
1209
|
model: metricsChunk?.model,
|
|
1088
1210
|
iterations: metricsChunk?.iterations,
|
|
1089
1211
|
executionTimeMs: metricsChunk?.executionTimeMs,
|
|
1212
|
+
executionId,
|
|
1090
1213
|
raw
|
|
1091
1214
|
};
|
|
1092
1215
|
}
|
|
@@ -1095,13 +1218,18 @@ async function invokeAgent(cfg, params) {
|
|
|
1095
1218
|
return {
|
|
1096
1219
|
content: responseChunk.content,
|
|
1097
1220
|
contentSource: "response",
|
|
1098
|
-
sessionId
|
|
1221
|
+
sessionId,
|
|
1099
1222
|
model: metricsChunk?.model,
|
|
1100
1223
|
usage: metricsChunk?.usage,
|
|
1101
1224
|
sources: responseChunk.sources,
|
|
1102
1225
|
blocks: responseChunk.blocks,
|
|
1226
|
+
gated: responseChunk.gated,
|
|
1103
1227
|
iterations: metricsChunk?.iterations,
|
|
1104
1228
|
executionTimeMs: metricsChunk?.executionTimeMs,
|
|
1229
|
+
executionId,
|
|
1230
|
+
outcome: doneChunk?.outcome,
|
|
1231
|
+
truncated: doneChunk?.truncated,
|
|
1232
|
+
finishReason: doneChunk?.finishReason,
|
|
1105
1233
|
raw
|
|
1106
1234
|
};
|
|
1107
1235
|
}
|
|
@@ -1706,7 +1834,10 @@ function signRequest(payload, secretKey, timestamp) {
|
|
|
1706
1834
|
OPERATION_TIMEOUT_MS,
|
|
1707
1835
|
ZihinAgentClient,
|
|
1708
1836
|
ZihinAgentError,
|
|
1837
|
+
classifyStreamErrorKind,
|
|
1709
1838
|
createClient,
|
|
1839
|
+
errorFromDoneChunk,
|
|
1840
|
+
errorFromStreamChunk,
|
|
1710
1841
|
isCommercialBlock,
|
|
1711
1842
|
sanitizeApiError,
|
|
1712
1843
|
signRequest,
|
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
|
|
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:
|
|
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 =
|
|
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:
|
|
885
|
+
requestId: safeId(d.request_id),
|
|
864
886
|
agentId: str(d.agent_id),
|
|
865
|
-
sessionId:
|
|
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,25 @@ 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:
|
|
913
|
+
return { type: "session", sessionId: safeId(d.session_id), data: e.data };
|
|
892
914
|
case "done":
|
|
893
|
-
return {
|
|
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:
|
|
928
|
+
code: safeLabel(err.code),
|
|
899
929
|
message: str(err.message),
|
|
900
|
-
requestId:
|
|
930
|
+
requestId: safeId(err.request_id),
|
|
931
|
+
timedOut: typeof err.timed_out === "boolean" ? err.timed_out : void 0,
|
|
901
932
|
data: e.data
|
|
902
933
|
};
|
|
903
934
|
}
|
|
@@ -905,7 +936,8 @@ function mapEvent(e) {
|
|
|
905
936
|
return {
|
|
906
937
|
type: "response",
|
|
907
938
|
content: str(d.content) ?? "",
|
|
908
|
-
sessionId:
|
|
939
|
+
sessionId: safeId(d.session_id),
|
|
940
|
+
gated: d.gated === true ? true : void 0,
|
|
909
941
|
sources: Array.isArray(d.sources) ? d.sources : void 0,
|
|
910
942
|
blocks: Array.isArray(d.blocks) ? d.blocks : void 0,
|
|
911
943
|
data: e.data
|
|
@@ -918,6 +950,7 @@ function mapEvent(e) {
|
|
|
918
950
|
model: str(modelConfig.model) ?? str(d.model),
|
|
919
951
|
iterations: num(d.iterations),
|
|
920
952
|
executionTimeMs: num(d.execution_time_ms),
|
|
953
|
+
executionId: safeId(d.execution_id),
|
|
921
954
|
data: e.data
|
|
922
955
|
};
|
|
923
956
|
}
|
|
@@ -1016,6 +1049,91 @@ async function* streamAgent(cfg, params) {
|
|
|
1016
1049
|
deadline.dispose();
|
|
1017
1050
|
}
|
|
1018
1051
|
}
|
|
1052
|
+
function classifyStreamErrorKind(code, timedOut) {
|
|
1053
|
+
if (timedOut === true) return "timeout";
|
|
1054
|
+
if (timedOut === false) return "server";
|
|
1055
|
+
return /TIMEOUT/i.test(code) ? "timeout" : "server";
|
|
1056
|
+
}
|
|
1057
|
+
function errorFromStreamChunk(chunk) {
|
|
1058
|
+
const code = chunk.code ?? "AGENT_ERROR";
|
|
1059
|
+
const kind = classifyStreamErrorKind(code, chunk.timedOut);
|
|
1060
|
+
return new ZihinAgentError(kind, sanitizeApiError(chunk.message ?? code).message || code, {
|
|
1061
|
+
code,
|
|
1062
|
+
requestId: chunk.requestId,
|
|
1063
|
+
retryable: false
|
|
1064
|
+
});
|
|
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
|
+
}
|
|
1019
1137
|
async function invokeAgent(cfg, params) {
|
|
1020
1138
|
const raw = [];
|
|
1021
1139
|
const keepRaw = params.includeRaw === true;
|
|
@@ -1023,26 +1141,27 @@ async function invokeAgent(cfg, params) {
|
|
|
1023
1141
|
let metricsChunk;
|
|
1024
1142
|
let metadataSessionId;
|
|
1025
1143
|
let mintedSessionId;
|
|
1026
|
-
let
|
|
1144
|
+
let doneChunk;
|
|
1145
|
+
let blocked;
|
|
1027
1146
|
let tokenBuffer = "";
|
|
1028
1147
|
for await (const chunk of streamAgent(cfg, params)) {
|
|
1029
1148
|
if (keepRaw) raw.push(chunk);
|
|
1030
1149
|
if (chunk.type === "error") {
|
|
1031
|
-
|
|
1032
|
-
const kind = /TIMEOUT/i.test(code) ? "timeout" : "server";
|
|
1033
|
-
throw new ZihinAgentError(kind, sanitizeApiError(chunk.message ?? code).message || code, {
|
|
1034
|
-
code,
|
|
1035
|
-
requestId: chunk.requestId,
|
|
1036
|
-
retryable: false
|
|
1037
|
-
});
|
|
1150
|
+
throw errorFromStreamChunk(chunk);
|
|
1038
1151
|
}
|
|
1039
1152
|
if (chunk.type === "response") responseChunk = chunk;
|
|
1040
1153
|
else if (chunk.type === "metrics") metricsChunk = chunk;
|
|
1041
1154
|
else if (chunk.type === "token") tokenBuffer += chunk.content;
|
|
1042
1155
|
else if (chunk.type === "metadata" && chunk.sessionId) metadataSessionId = chunk.sessionId;
|
|
1043
1156
|
else if (chunk.type === "session" && chunk.sessionId) mintedSessionId = chunk.sessionId;
|
|
1044
|
-
else if (chunk.type === "done")
|
|
1045
|
-
|
|
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;
|
|
1046
1165
|
if (!responseChunk) {
|
|
1047
1166
|
if (cleanEnd) {
|
|
1048
1167
|
throw new ZihinAgentError("protocol", "Stream ended (done) without a `response` event.");
|
|
@@ -1051,11 +1170,12 @@ async function invokeAgent(cfg, params) {
|
|
|
1051
1170
|
return {
|
|
1052
1171
|
content: tokenBuffer,
|
|
1053
1172
|
contentSource: "tokens",
|
|
1054
|
-
sessionId
|
|
1173
|
+
sessionId,
|
|
1055
1174
|
usage: metricsChunk?.usage,
|
|
1056
1175
|
model: metricsChunk?.model,
|
|
1057
1176
|
iterations: metricsChunk?.iterations,
|
|
1058
1177
|
executionTimeMs: metricsChunk?.executionTimeMs,
|
|
1178
|
+
executionId,
|
|
1059
1179
|
raw
|
|
1060
1180
|
};
|
|
1061
1181
|
}
|
|
@@ -1064,13 +1184,18 @@ async function invokeAgent(cfg, params) {
|
|
|
1064
1184
|
return {
|
|
1065
1185
|
content: responseChunk.content,
|
|
1066
1186
|
contentSource: "response",
|
|
1067
|
-
sessionId
|
|
1187
|
+
sessionId,
|
|
1068
1188
|
model: metricsChunk?.model,
|
|
1069
1189
|
usage: metricsChunk?.usage,
|
|
1070
1190
|
sources: responseChunk.sources,
|
|
1071
1191
|
blocks: responseChunk.blocks,
|
|
1192
|
+
gated: responseChunk.gated,
|
|
1072
1193
|
iterations: metricsChunk?.iterations,
|
|
1073
1194
|
executionTimeMs: metricsChunk?.executionTimeMs,
|
|
1195
|
+
executionId,
|
|
1196
|
+
outcome: doneChunk?.outcome,
|
|
1197
|
+
truncated: doneChunk?.truncated,
|
|
1198
|
+
finishReason: doneChunk?.finishReason,
|
|
1074
1199
|
raw
|
|
1075
1200
|
};
|
|
1076
1201
|
}
|
|
@@ -1674,7 +1799,10 @@ export {
|
|
|
1674
1799
|
OPERATION_TIMEOUT_MS,
|
|
1675
1800
|
ZihinAgentClient,
|
|
1676
1801
|
ZihinAgentError,
|
|
1802
|
+
classifyStreamErrorKind,
|
|
1677
1803
|
createClient,
|
|
1804
|
+
errorFromDoneChunk,
|
|
1805
|
+
errorFromStreamChunk,
|
|
1678
1806
|
isCommercialBlock,
|
|
1679
1807
|
sanitizeApiError,
|
|
1680
1808
|
signRequest,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zihin/agent-client",
|
|
3
|
-
"version": "0.
|
|
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",
|