@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 +21 -0
- package/README.md +31 -1
- package/dist/index.d.mts +101 -6
- package/dist/index.d.ts +101 -6
- package/dist/index.js +135 -17
- package/dist/index.mjs +134 -17
- 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
|
@@ -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
|
|
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
|
-
|
|
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';
|
|
@@ -250,9 +304,20 @@ type AgentStreamChunk = {
|
|
|
250
304
|
sessionId?: string;
|
|
251
305
|
data: unknown;
|
|
252
306
|
}
|
|
253
|
-
/**
|
|
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 =
|
|
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
|
-
|
|
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';
|
|
@@ -250,9 +304,20 @@ type AgentStreamChunk = {
|
|
|
250
304
|
sessionId?: string;
|
|
251
305
|
data: unknown;
|
|
252
306
|
}
|
|
253
|
-
/**
|
|
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 =
|
|
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
|
|
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:
|
|
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 =
|
|
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:
|
|
919
|
+
requestId: safeId(d.request_id),
|
|
897
920
|
agentId: str(d.agent_id),
|
|
898
|
-
sessionId:
|
|
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:
|
|
947
|
+
return { type: "session", sessionId: safeId(d.session_id), data: e.data };
|
|
925
948
|
case "done":
|
|
926
|
-
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
|
+
};
|
|
927
958
|
case "error": {
|
|
928
959
|
const err = rec(d.error);
|
|
929
960
|
return {
|
|
930
961
|
type: "error",
|
|
931
|
-
code:
|
|
962
|
+
code: safeLabel(err.code),
|
|
932
963
|
message: str(err.message),
|
|
933
|
-
requestId:
|
|
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:
|
|
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
|
|
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")
|
|
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
|
|
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
|
|
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
|
|
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,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:
|
|
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),
|
|
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:
|
|
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
|
|
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")
|
|
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
|
|
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
|
|
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.
|
|
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",
|