@zihin/agent-client 0.6.1 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -23,7 +23,8 @@ simples, e oferece **dois estilos** de invocação:
23
23
  npm install @zihin/agent-client
24
24
  ```
25
25
 
26
- Requer Node >= 18 (usa `fetch`/`ReadableStream` globais).
26
+ Requer Node >= 20.16 (usa `fetch`/`ReadableStream` globais; o `signRequest` do build ESM usa
27
+ `process.getBuiltinModule`, 20.16+). Node 18 saiu de suporte em 04/2025 e deixou de ser testado a partir da 0.8.
27
28
 
28
29
  ## Uso — caminho direto
29
30
 
@@ -148,6 +149,55 @@ r.suggestions; // ['Caneta A', 'Caneta B'] — ou undefined quando o agente não
148
149
  servidor os lista em `ignoredFields`; o núcleo emite um `ZihinDeprecationWarning` **uma vez por
149
150
  processo** (`ZIHIN_DEP001`/`ZIHIN_DEP002`). Saem do SDK na 1.0.
150
151
 
152
+ ### Idempotência, recusa do provedor e limite de contexto (0.7)
153
+
154
+ > Backend: zihin-agent-builder `server-llm` de outubro/2026 (`client_message_id` em `121e426f`,
155
+ > `REFUSED` em `4cea8d53`, `CONTEXT_LIMIT` em `e670f530`, trava de sessão em `b67141d7`). Verificado
156
+ > em `9ec79cfc` (03/11/2026).
157
+
158
+ ```ts
159
+ const r = await zihin.invokeAgent({
160
+ agentId,
161
+ message: 'Fechar o pedido 42',
162
+ clientMessageId: crypto.randomUUID(), // um UUID novo por mensagem; reenvie o MESMO para retentar
163
+ });
164
+ r.idempotentReplay; // true = o BE repetiu a resposta gravada (o agente não rodou de novo)
165
+ ```
166
+
167
+ - **`clientMessageId`** (UUID) torna o turno idempotente. Com ele, o núcleo passa a **retentar o
168
+ POST** do `/stream` em erro de rede e 5xx antes do stream abrir (sem ele, POST nunca retenta em
169
+ rede — poderia duplicar o turno). Reenviar o mesmo id com a mesma mensagem: turno ainda em voo →
170
+ `SESSION_BUSY` (`blocked`, retentável); turno concluído com `COMPLETED`/`LENGTH` → o BE repete a
171
+ resposta gravada por 24 h, com `idempotentReplay: true` e sem `usage`. Mesmo id com **outra**
172
+ mensagem → `IDEMPOTENCY_CONFLICT` (`config`). **A garantia cobre só o turno concluído**: turno
173
+ cortado no meio do stream, cancelado, com erro ou timeout libera o id e o reenvio roda de novo
174
+ (tools incluídas); por isso o retry automático é só pré-stream, e é best-effort (Redis do BE fora →
175
+ sem dedupe). O escopo é a API key + `consumerKey` (não a sessão) e a comparação é só
176
+ mensagem+anexos: **nunca use um valor fixo** (uma automação agendada receberia a mesma resposta por
177
+ 24 h); reusar o id noutra sessão dá `config` (`IDEMPOTENT_REPLAY_OTHER_SESSION`; comparação de
178
+ sessão case-insensitive, o BE devolve o `session_id` em minúsculas). Essa guarda só existe em
179
+ `invokeAgent` **com `sessionId` explícito**: com `sessionFrom`, sem sessão, ou em `streamAgent`,
180
+ um id reusado devolve em silêncio a resposta (e o `session_id`) da conversa original. O BE ecoa o id
181
+ em minúsculas. `SERVICE_UNAVAILABLE` (replay sem resposta recuperável) **não é retentável**: pode
182
+ durar 24 h e só um `clientMessageId` novo sai dele.
183
+ Contra um servidor que não liste `client_message_id` em `accepted_fields`, o núcleo recusa o turno
184
+ (`config`): a garantia que você pediu não existiria.
185
+ - **`REFUSED`**: o provedor do modelo recusou por política. O BE entrega um texto neutro fixo como
186
+ `response` e marca a execução como erro; o núcleo **lança** `blocked` com `code: 'REFUSED'` (na 0.6
187
+ o texto neutro saía como resposta). **`CONTEXT_LIMIT`**: a conversa reuniu mais contexto do que o
188
+ modelo processa — `blocked`, `code: 'CONTEXT_LIMIT'`; peça menos ou abra outra sessão.
189
+ - **Trava de um turno por sessão** (ligada por padrão no BE desde 18/10/2026): mandar a segunda
190
+ mensagem antes da primeira terminar dá `SESSION_BUSY` como `event: error`. O núcleo o classifica
191
+ `blocked` **retentável** (o turno nem começou) — antes saía como `server` não retentável.
192
+ - **Ids do turno** em `AgentResult`: `executionId` agora vem do `metadata` (chega mesmo quando o
193
+ turno morre antes do `metrics`), mais `clientMessageId`, `userMessageId`, `assistantMessageId`.
194
+ - **`capabilities.blocks`** aceita `'html_report'`: a tela renderiza HTML, e o BE orienta o agente a
195
+ entregar, quando couber, um documento HTML num bloco ```html dentro do `content` (não é bloco novo).
196
+ - **`dispatchAgent` (webhook sync)** lê o `outcome` do corpo: `REFUSED`, `CONTEXT_LIMIT`, `TIMEOUT`,
197
+ `ERROR`… chegam com `success: true` e a frase da plataforma em `agent_response`; o núcleo lança a
198
+ mesma classificação do `/stream`. `DispatchResult.outcome` expõe os que respondem.
199
+ - Novo helper exportado `errorFromOutcome(outcome, ctx)` para quem lê desfechos fora do SSE.
200
+
151
201
  ## Uso — caminho assíncrono (triggers)
152
202
 
153
203
  ```ts
@@ -275,8 +325,8 @@ direto aplica a mesma regra com `errorFromDoneChunk(chunk, { executionId, sessio
275
325
 
276
326
  O mesmo vale para os outros desfechos em que o `content` é falha ou recusa do motor, e não
277
327
  resposta: `ERROR`/`CANCELLED` (pelo servidor) → `kind: 'server'`, `QUOTA_EXCEEDED` → `quota_exceeded`,
278
- `SUSPENDED`/`BUDGET_EXCEEDED`/`SESSION_BUSY`/`CONFLICT` e o `PAUSED` de agente pausado/arquivado →
279
- `blocked` (o `code` é o `outcome`; só `SESSION_BUSY` é retentável). Um `ERROR` precedido de
328
+ `SUSPENDED`/`BUDGET_EXCEEDED`/`SESSION_BUSY`/`CONFLICT`/`REFUSED`/`CONTEXT_LIMIT` e o `PAUSED` de
329
+ agente pausado/arquivado → `blocked` (o `code` é o `outcome`; só `SESSION_BUSY` é retentável). Um `ERROR` precedido de
280
330
  `status.blocked` (gate da plataforma) também sai `blocked`, com o motivo em `code`
281
331
  (`ownership_denied` → `not_found`).
282
332
 
@@ -302,8 +352,9 @@ const agents = await zihin.listAgents(); // GET /api/v2/agents
302
352
 
303
353
  ## Erros
304
354
 
305
- Todos os erros são `ZihinAgentError` com `kind` (`config | auth | not_found | rate_limit | timeout |
306
- aborted | server | network | protocol | unknown`), `status`, `code`, `retryable` e — para depurar em
355
+ Todos os erros são `ZihinAgentError` com `kind` (`config | auth | trial_expired | no_subscription |
356
+ quota_exceeded | not_found | rate_limit | timeout | aborted | server | network | protocol | blocked |
357
+ unknown`), `status`, `code`, `retryable` e — para depurar em
307
358
  produção externa — `endpoint` (`"POST host/path"`), `attempts` e **`requestId`** (handle do BE para o
308
359
  suporte correlacionar). Erros de rede carregam a causa-raiz (`ECONNREFUSED`/`ENOTFOUND`/TLS…) na
309
360
  mensagem. As mensagens redigem **só segredos** (key/Bearer/stack) — `request_id`/`trace_id` são
@@ -345,7 +396,15 @@ correlacionar com o console.
345
396
  da tentativa seguinte — o que fica limitado é o tempo gasto com retries.
346
397
  - **Retry sem duplicar efeito** — GET (idempotente) retenta em rede/429/5xx; POST com efeito
347
398
  (`invokeAgent`/`dispatchAgent`/`createTrigger`) retenta **só em 429/503** e **nunca em erro de
348
- rede** (o request pode ter sido processado). Backoff exponencial 1s→10s com jitter.
399
+ rede** (o request pode ter sido processado). Exceção: `invokeAgent` com `clientMessageId` retenta
400
+ também em rede e 5xx, **só antes dos headers do SSE chegarem**. É seguro porque o BE envia os
401
+ headers antes de reservar o id e antes de qualquer trabalho do turno; se o 1º POST chegou a rodar,
402
+ a chave transforma o reenvio em `SESSION_BUSY` ou replay. Um stream **cortado no meio** sai
403
+ `network` com `retryable: false`: o BE cancela o turno e libera o id, então reenviar (mesmo com a
404
+ chave) roda as tools de novo. Contra um BE antigo que ainda não aceite `client_message_id`, o
405
+ retry pré-headers acontece sem a chave valer (o turno falha depois em `config`); a ordem
406
+ headers-antes-do-trabalho é a mesma, mas a garantia depende do BE. Backoff exponencial 1s→10s
407
+ com jitter.
349
408
  - **Anti-DoS de memória** — limites de stream `maxStreamEventBytes` (1MB) e `maxStreamTotalBytes`
350
409
  (64MB); estourar aborta com `protocol`. Configuráveis no `createClient`.
351
410
  - **`includeRaw`** — por padrão `invokeAgent` não retém os eventos brutos (`raw: []`) para economizar
@@ -360,7 +419,7 @@ correlacionar com o console.
360
419
  `delayMs`/`reason`/`status`) e `onError(err)` — chamado quando uma operação falha em definitivo
361
420
  (inclusive `aborted`; filtre por `err.kind`). Exceção lançada dentro do hook é engolida e nunca
362
421
  mascara o erro original.
363
- - **`fetch` injetável** — `fetch?` na config; por padrão usa `globalThis.fetch` (Node ≥18). Um
422
+ - **`fetch` injetável** — `fetch?` na config; por padrão usa `globalThis.fetch` (Node ≥20.16). Um
364
423
  preflight dá erro `config` claro se não houver fetch.
365
424
  - **Portável** — sem `Buffer`; `node:crypto` só é carregado se você usar `signRequest`. Roda em Node;
366
425
  para edge/serverless, injete `fetch` se necessário. O pacote publica **CJS e ESM** (`exports` map):
package/dist/index.d.mts CHANGED
@@ -146,8 +146,11 @@ interface InvokeOptions {
146
146
  */
147
147
  jitMode?: boolean;
148
148
  }
149
- /** Blocos estruturados que a sua interface sabe renderizar (`capabilities.blocks`). */
150
- type ClientBlockType = 'quick_replies';
149
+ /**
150
+ * Blocos estruturados que a sua interface sabe renderizar (`capabilities.blocks`). Valor que o BE
151
+ * não conhece sai em `ignoredFields` como `capabilities.blocks.<v>` + `not_supported` (aviso, não erro).
152
+ */
153
+ type ClientBlockType = 'quick_replies' | 'html_report' | (string & {});
151
154
  /**
152
155
  * O que a SUA tela sabe mostrar. O agente só oferece o que você declarar — sem declarar, nada é
153
156
  * gerado e nada é cobrado. Não exige privilégio de API key.
@@ -158,6 +161,11 @@ interface ClientCapabilities {
158
161
  * vira a próxima mensagem). Chegam em `AgentResult.suggestions` (e no bloco `quick_replies` de
159
162
  * `blocks`). O dono do agente pode desligá-las (`quick_replies_enabled`); o agente decide quando
160
163
  * vale sugerir — nem todo turno traz sugestões.
164
+ *
165
+ * `html_report`: a tela renderiza HTML. Não cria bloco novo: o BE orienta o agente a entregar,
166
+ * quando couber, um documento HTML completo num bloco de código ```html dentro do `content`
167
+ * (BE `14d2a43c`, 19/10/2026). Só declare se você realmente renderiza HTML — senão o usuário
168
+ * recebe markup cru.
161
169
  */
162
170
  blocks?: ClientBlockType[];
163
171
  }
@@ -184,6 +192,28 @@ interface InvokeAgentParams {
184
192
  attachments?: Attachment[];
185
193
  /** Continuidade de conversa (Supabase agent_sessions). Não combina com `sessionFrom`. */
186
194
  sessionId?: string;
195
+ /**
196
+ * Chave de idempotência do turno (UUID, qualquer versão; BE normaliza para minúsculas).
197
+ * Reenviar a MESMA mensagem com o mesmo id não roda o agente de novo: enquanto o turno original
198
+ * está em voo o BE responde `SESSION_BUSY` (`blocked`, retentável); depois de concluído com
199
+ * `COMPLETED`/`LENGTH` o BE **repete a resposta gravada** (`AgentResult.idempotentReplay: true`,
200
+ * sem `usage`, por 24 h). Mesmo id com OUTRA mensagem → `IDEMPOTENCY_CONFLICT` (`config`).
201
+ * Turno que terminou em erro/timeout/gate libera o id: o reenvio roda de novo.
202
+ *
203
+ * Com ele, o núcleo passa a **retentar o POST** do `/stream` em erro de rede e 5xx antes do
204
+ * stream abrir (sem ele, POST nunca retenta em rede — poderia duplicar o turno).
205
+ *
206
+ * ⚠️ **O que a garantia cobre:** só não repetir um turno **concluído** (`COMPLETED`/`LENGTH`).
207
+ * Turno cortado no meio do stream, cancelado, com erro ou timeout **libera o id** e o reenvio
208
+ * roda de novo — inclusive tools de efeito colateral que já rodaram. É best-effort (Redis do BE
209
+ * fora → sem dedupe, campo ainda aceito). O escopo é API key + `consumerKey` (não a sessão) e a
210
+ * comparação é só mensagem+anexos: **nunca use um valor fixo** — um workflow agendado com a
211
+ * mesma mensagem receberia a mesma resposta por 24 h; reusar o id noutra sessão dá erro
212
+ * `config` (`IDEMPOTENT_REPLAY_OTHER_SESSION`). O BE devolve o id em minúsculas. Vale sob
213
+ * qualquer papel de API key. Enviado como `client_message_id`; contra um BE que não o aceite
214
+ * (`accepted_fields` sem ele) o núcleo recusa o turno (`config`).
215
+ */
216
+ clientMessageId?: string;
187
217
  /**
188
218
  * Identidade da pessoa do outro lado (memória por pessoa, denylist, gate de engajamento). Só vale
189
219
  * sob API key **admin/owner**. Para continuar conversas que um webhook já gravava, use o MESMO
@@ -285,8 +315,23 @@ interface AgentResult {
285
315
  suggestions?: string[];
286
316
  iterations?: number;
287
317
  executionTimeMs?: number;
288
- /** `execution_id` do turno (evento `metrics`) — handle de correlação no console/telemetria. */
318
+ /**
319
+ * `execution_id` do turno — handle de correlação no console/telemetria. Lido do `metadata`
320
+ * (BE desde 17/10/2026) com fallback no `metrics`; ausente num turno barrado por gate.
321
+ */
289
322
  executionId?: string;
323
+ /** `client_message_id` que o BE aplicou ao turno (eco do que você mandou, em **minúsculas**). */
324
+ clientMessageId?: string;
325
+ /**
326
+ * `true` = esta é a resposta GRAVADA de um turno anterior com o mesmo `clientMessageId`
327
+ * (reenvio idempotente). O agente não rodou de novo: sem `usage`/`model`/`iterations`, e
328
+ * `truncated`/`finishReason` não vêm mesmo que o original fosse `LENGTH`.
329
+ */
330
+ idempotentReplay?: boolean;
331
+ /** Id da mensagem do usuário gravada na sessão (quando o BE o deu). */
332
+ userMessageId?: string;
333
+ /** Id da mensagem do agente gravada na sessão (quando o BE o deu). */
334
+ assistantMessageId?: string;
290
335
  /** Campos do pedido que o BE aplicou (`metadata.accepted_fields`). Ausente = backend antigo. */
291
336
  acceptedFields?: string[];
292
337
  /** Campos do pedido que o BE recebeu e ignorou (`metadata.ignored_fields`). */
@@ -301,8 +346,9 @@ interface AgentResult {
301
346
  * Desfecho do turno, lido do `done` (`exitReason` do motor). `undefined` = backend antigo ou
302
347
  * stream cortado antes do `done`. Só chegam aqui os desfechos cujo `content` é resposta
303
348
  * (`COMPLETED`, `LENGTH`), o `PAUSED` (aprovação pendente ou agente pausado) e desfechos novos.
304
- * `TIMEOUT`, `ERROR`, `CANCELLED`, `SUSPENDED` e os de bloqueio viram `ZihinAgentError` — o
305
- * `content` desses turnos é a mensagem de falha/recusa do motor (`errorFromDoneChunk`).
349
+ * `TIMEOUT`, `ERROR`, `CANCELLED`, `SUSPENDED`, `REFUSED`, `CONTEXT_LIMIT` e os de bloqueio
350
+ * viram `ZihinAgentError` — o `content` desses turnos é a mensagem de falha/recusa do motor
351
+ * (`errorFromDoneChunk`).
306
352
  */
307
353
  outcome?: AgentOutcome;
308
354
  /** `true` quando a resposta foi cortada pelo teto de saída do modelo (`done.truncated`). */
@@ -317,7 +363,11 @@ interface AgentResult {
317
363
  * contam como sucesso no BE (`isSuccessfulOutcome`); os demais valores são repassados como vêm.
318
364
  * Aberto (`string & {}`) porque o BE pode acrescentar desfechos (ex.: os de bloqueio).
319
365
  */
320
- type AgentOutcome = 'COMPLETED' | 'LENGTH' | 'TIMEOUT' | 'ERROR' | 'CANCELLED' | 'PAUSED' | 'SUSPENDED' | 'BUDGET_EXCEEDED' | 'QUOTA_EXCEEDED' | 'SESSION_BUSY' | 'CONFLICT' | (string & {});
366
+ type AgentOutcome = 'COMPLETED' | 'LENGTH' | 'TIMEOUT' | 'ERROR' | 'CANCELLED' | 'PAUSED' | 'SUSPENDED' | 'BUDGET_EXCEEDED' | 'QUOTA_EXCEEDED' | 'SESSION_BUSY' | 'CONFLICT'
367
+ /** O provedor do modelo recusou por política (desde 08/10/2026). O `content` é um texto neutro fixo. */
368
+ | 'REFUSED'
369
+ /** A conversa reuniu mais contexto do que o modelo processa (desde 14/10/2026). No `/stream` chega como `event: error`. */
370
+ | 'CONTEXT_LIMIT' | (string & {});
321
371
  /**
322
372
  * Qual relógio encerrou o turno (contrato de tempo §2): `turn` (prazo do turno, inclusive o
323
373
  * `options.timeoutMs` pedido), `first_signal` (o provedor não começou a responder), `idle`
@@ -341,6 +391,11 @@ type AgentStreamChunk =
341
391
  requestId?: string;
342
392
  agentId?: string;
343
393
  sessionId?: string;
394
+ /** `execution_id` do turno (BE desde 17/10/2026). Ausente num turno barrado por gate. */
395
+ executionId?: string;
396
+ clientMessageId?: string;
397
+ /** `true` = o BE vai repetir a resposta gravada (mesmo `clientMessageId`), sem rodar o agente. */
398
+ idempotentReplay?: boolean;
344
399
  acceptedFields?: string[];
345
400
  ignoredFields?: IgnoredField[];
346
401
  data: unknown;
@@ -371,6 +426,12 @@ type AgentStreamChunk =
371
426
  } | {
372
427
  type: 'action_proposed';
373
428
  data: unknown;
429
+ }
430
+ /** Aprovação humana encaminhada ao aprovador (`{ action_id, kind, title, expires_at }`); o turno fica `PAUSED`. */
431
+ | {
432
+ type: 'action_forwarded';
433
+ actionId?: string;
434
+ data: unknown;
374
435
  } | {
375
436
  type: 'token';
376
437
  content: string;
@@ -382,7 +443,9 @@ type AgentStreamChunk =
382
443
  }
383
444
  /**
384
445
  * O BE encerra o stream com `event: error` + `{ error: { type, code, message, request_id,
385
- * timed_out? } }` (`utils/sse.js`) — inclusive quando o TURNO morre no deadline. Antes isto
446
+ * timed_out? } }` (`utils/sse.js`) — inclusive quando o TURNO morre no deadline. O BE NÃO põe
447
+ * `status` nem `details` no fio (ficam no servidor): `SESSION_BUSY`, `IDEMPOTENCY_CONFLICT`,
448
+ * `CONTEXT_LIMIT`, `CONFLICT` e os de quota chegam só com `code`+`message`. Antes isto
386
449
  * caía em `unknown` e o `invokeAgent` ignorava: se já tinha chegado token, devolvia a
387
450
  * deliberação crua como SUCESSO.
388
451
  *
@@ -422,6 +485,11 @@ type AgentStreamChunk =
422
485
  timeoutClock?: TimeoutClock;
423
486
  truncated?: boolean;
424
487
  finishReason?: string;
488
+ executionId?: string;
489
+ clientMessageId?: string;
490
+ idempotentReplay?: boolean;
491
+ userMessageId?: string;
492
+ assistantMessageId?: string;
425
493
  data: unknown;
426
494
  } | {
427
495
  type: 'response';
@@ -436,6 +504,11 @@ type AgentStreamChunk =
436
504
  suggestions?: string[];
437
505
  /** Resposta ENLATADA de um gate (handoff humano, denylist, sessão suspensa) — não do agente. */
438
506
  gated?: boolean;
507
+ executionId?: string;
508
+ clientMessageId?: string;
509
+ idempotentReplay?: boolean;
510
+ userMessageId?: string;
511
+ assistantMessageId?: string;
439
512
  data: unknown;
440
513
  } | {
441
514
  type: 'metrics';
@@ -557,7 +630,7 @@ interface TriggerInfo {
557
630
  interface ListTriggersParams {
558
631
  /** Filtra por agente. */
559
632
  agentId?: string;
560
- /** Filtra por tipo (webhook | db_event | email | schedule). */
633
+ /** Filtra por tipo (`webhook` | `email` | `schedule`; `db_event` foi removido do BE em 03/10/2026). */
561
634
  triggerType?: string;
562
635
  /** Apenas habilitados. */
563
636
  enabledOnly?: boolean;
@@ -586,9 +659,19 @@ interface DispatchResult {
586
659
  accepted: boolean;
587
660
  triggerId?: string;
588
661
  agentId?: string;
662
+ /** Id da execução do TRIGGER (`trigger_executions`), não o `execution_id` do turno do agente. */
589
663
  executionId?: string;
590
664
  /** Presente quando o trigger é `sync`: resposta imediata do agente. */
591
665
  agentResponse?: string;
666
+ /**
667
+ * Desfecho do turno no modo `sync` (BE desde 14/10/2026; ausente = backend antigo). Só chegam
668
+ * aqui os desfechos cujo `agentResponse` é resposta (`COMPLETED`, `LENGTH`) e o `PAUSED`; os
669
+ * demais (`REFUSED`, `CONTEXT_LIMIT`, `TIMEOUT`, `ERROR`…) viram `ZihinAgentError` mesmo com
670
+ * `success: true` no corpo — o BE mantém `success` como "entregou", e o texto é a frase da
671
+ * plataforma, não uma resposta. Limite: o sync não traz `status.blocked`, então o `PAUSED` de
672
+ * agente pausado/arquivado chega aqui com o texto de recusa em `agentResponse`.
673
+ */
674
+ outcome?: AgentOutcome;
592
675
  usage?: Usage;
593
676
  /** Iterações do laço de tools no turno (sync). */
594
677
  iterations?: number;
@@ -868,15 +951,32 @@ declare function createClient(config: ZihinClientConfig): ZihinAgentClient;
868
951
  * O regex no nome só vale como FALLBACK para backends que ainda não emitem `timed_out`
869
952
  * (self-hosted/antigos). Some numa major futura, depois do rename no BE.
870
953
  */
871
- declare function classifyStreamErrorKind(code: string, timedOut?: boolean): 'timeout' | 'server' | 'config';
954
+ declare function classifyStreamErrorKind(code: string, timedOut?: boolean): ZihinErrorKind;
872
955
  /**
873
956
  * Converte o chunk `type: 'error'` (evento terminal do BE) num `ZihinAgentError` — a MESMA regra
874
957
  * que o `invokeAgent` aplica, exportada para quem consome `streamAgent` direto (o adapter do
875
958
  * Paperclip, por exemplo) não reimplementar a classificação por conta e divergir dela.
959
+ * `ctx` leva os ids já vistos no `metadata` (o `error` em si não os traz).
876
960
  */
877
961
  declare function errorFromStreamChunk(chunk: Extract<AgentStreamChunk, {
878
962
  type: 'error';
879
- }>): ZihinAgentError;
963
+ }>, ctx?: {
964
+ executionId?: string;
965
+ sessionId?: string;
966
+ }): ZihinAgentError;
967
+ /**
968
+ * Converte um `outcome` cru (fora do `/stream`: `outcome` do webhook sync, `exit_reason` de uma
969
+ * execução) num `ZihinAgentError`, ou `undefined` quando o texto que o acompanha É resposta
970
+ * (`COMPLETED`, `LENGTH`, `PAUSED`, desconhecido/ausente). Mesma tabela do `errorFromDoneChunk`,
971
+ * sem o contexto de `status.blocked` que só o SSE tem.
972
+ */
973
+ declare function errorFromOutcome(outcome: string | undefined, ctx?: {
974
+ executionId?: string;
975
+ sessionId?: string;
976
+ status?: number;
977
+ endpoint?: string;
978
+ attempts?: number;
979
+ }): ZihinAgentError | undefined;
880
980
  /**
881
981
  * Converte o `done` de um turno que NÃO produziu resposta num `ZihinAgentError`; devolve
882
982
  * `undefined` quando o `content` é resposta. Mesma regra do `invokeAgent`, exportada para quem
@@ -991,7 +1091,7 @@ interface SignedRequestHeaders {
991
1091
  *
992
1092
  * ⚠️ No build ESM depende de `process.getBuiltinModule` (Node >=20.16); em Node mais antigo via
993
1093
  * `import` lança `ZihinAgentError` com `kind: 'config'`. O build CJS funciona em qualquer Node
994
- * >=18. (Todo o resto do núcleo é Web-API puro e roda nos dois.)
1094
+ * suportado (>=20.16). (Todo o resto do núcleo é Web-API puro e roda nos dois.)
995
1095
  */
996
1096
  declare function signRequest(payload: string, secretKey: string, timestamp?: number): SignedRequestHeaders;
997
1097
 
@@ -1022,4 +1122,4 @@ declare function signRequest(payload: string, secretKey: string, timestamp?: num
1022
1122
  */
1023
1123
  declare const MAX_TASK_PAYLOAD_BYTES: number;
1024
1124
 
1025
- export { type AgentOutcome, type AgentResult, type AgentStreamChunk, type AgentSummary, type Attachment, type AttachmentType, type CancelTaskParams, type CancelTaskResult, type ClientBlockType, type ClientCapabilities, type ConsumerIdentity, type ContextValue, type CreateTriggerParams, DEADLINE_GRACE_MS, DEFAULT_LIMITS, type DelegateTaskParams, type DeleteTriggerParams, type DispatchAgentParams, type DispatchResult, type ExecutionStatus, type FindOrCreateResult, type GetExecutionParams, type GetTaskParams, type GetTriggerParams, type IgnoredField, type InvokeAgentParams, type InvokeOptions, type ListTasksParams, type ListTriggersParams, MAX_TASK_PAYLOAD_BYTES, OPERATION_TIMEOUT_MS, type QueryExtraction, type ResponseTransformMode, type RetryInfo, type SignedRequestHeaders, type Source, type TaskDossier, type TaskExecution, type TaskInfo, type TaskListPage, type TimeoutClock, type TimeoutOperation, type TriggerInfo, type TriggerMode, type TriggerRef, type UpdateTriggerParams, type Usage, ZihinAgentClient, ZihinAgentError, type ZihinClientConfig, type ZihinErrorKind, classifyStreamErrorKind, createClient, errorFromDoneChunk, errorFromStreamChunk, isCommercialBlock, sanitizeApiError, signRequest, validateApiKeyFormat, validateBaseUrl, validatePromptSize };
1125
+ export { type AgentOutcome, type AgentResult, type AgentStreamChunk, type AgentSummary, type Attachment, type AttachmentType, type CancelTaskParams, type CancelTaskResult, type ClientBlockType, type ClientCapabilities, type ConsumerIdentity, type ContextValue, type CreateTriggerParams, DEADLINE_GRACE_MS, DEFAULT_LIMITS, type DelegateTaskParams, type DeleteTriggerParams, type DispatchAgentParams, type DispatchResult, type ExecutionStatus, type FindOrCreateResult, type GetExecutionParams, type GetTaskParams, type GetTriggerParams, type IgnoredField, type InvokeAgentParams, type InvokeOptions, type ListTasksParams, type ListTriggersParams, MAX_TASK_PAYLOAD_BYTES, OPERATION_TIMEOUT_MS, type QueryExtraction, type ResponseTransformMode, type RetryInfo, type SignedRequestHeaders, type Source, type TaskDossier, type TaskExecution, type TaskInfo, type TaskListPage, type TimeoutClock, type TimeoutOperation, type TriggerInfo, type TriggerMode, type TriggerRef, type UpdateTriggerParams, type Usage, ZihinAgentClient, ZihinAgentError, type ZihinClientConfig, type ZihinErrorKind, classifyStreamErrorKind, createClient, errorFromDoneChunk, errorFromOutcome, errorFromStreamChunk, isCommercialBlock, sanitizeApiError, signRequest, validateApiKeyFormat, validateBaseUrl, validatePromptSize };
package/dist/index.d.ts CHANGED
@@ -146,8 +146,11 @@ interface InvokeOptions {
146
146
  */
147
147
  jitMode?: boolean;
148
148
  }
149
- /** Blocos estruturados que a sua interface sabe renderizar (`capabilities.blocks`). */
150
- type ClientBlockType = 'quick_replies';
149
+ /**
150
+ * Blocos estruturados que a sua interface sabe renderizar (`capabilities.blocks`). Valor que o BE
151
+ * não conhece sai em `ignoredFields` como `capabilities.blocks.<v>` + `not_supported` (aviso, não erro).
152
+ */
153
+ type ClientBlockType = 'quick_replies' | 'html_report' | (string & {});
151
154
  /**
152
155
  * O que a SUA tela sabe mostrar. O agente só oferece o que você declarar — sem declarar, nada é
153
156
  * gerado e nada é cobrado. Não exige privilégio de API key.
@@ -158,6 +161,11 @@ interface ClientCapabilities {
158
161
  * vira a próxima mensagem). Chegam em `AgentResult.suggestions` (e no bloco `quick_replies` de
159
162
  * `blocks`). O dono do agente pode desligá-las (`quick_replies_enabled`); o agente decide quando
160
163
  * vale sugerir — nem todo turno traz sugestões.
164
+ *
165
+ * `html_report`: a tela renderiza HTML. Não cria bloco novo: o BE orienta o agente a entregar,
166
+ * quando couber, um documento HTML completo num bloco de código ```html dentro do `content`
167
+ * (BE `14d2a43c`, 19/10/2026). Só declare se você realmente renderiza HTML — senão o usuário
168
+ * recebe markup cru.
161
169
  */
162
170
  blocks?: ClientBlockType[];
163
171
  }
@@ -184,6 +192,28 @@ interface InvokeAgentParams {
184
192
  attachments?: Attachment[];
185
193
  /** Continuidade de conversa (Supabase agent_sessions). Não combina com `sessionFrom`. */
186
194
  sessionId?: string;
195
+ /**
196
+ * Chave de idempotência do turno (UUID, qualquer versão; BE normaliza para minúsculas).
197
+ * Reenviar a MESMA mensagem com o mesmo id não roda o agente de novo: enquanto o turno original
198
+ * está em voo o BE responde `SESSION_BUSY` (`blocked`, retentável); depois de concluído com
199
+ * `COMPLETED`/`LENGTH` o BE **repete a resposta gravada** (`AgentResult.idempotentReplay: true`,
200
+ * sem `usage`, por 24 h). Mesmo id com OUTRA mensagem → `IDEMPOTENCY_CONFLICT` (`config`).
201
+ * Turno que terminou em erro/timeout/gate libera o id: o reenvio roda de novo.
202
+ *
203
+ * Com ele, o núcleo passa a **retentar o POST** do `/stream` em erro de rede e 5xx antes do
204
+ * stream abrir (sem ele, POST nunca retenta em rede — poderia duplicar o turno).
205
+ *
206
+ * ⚠️ **O que a garantia cobre:** só não repetir um turno **concluído** (`COMPLETED`/`LENGTH`).
207
+ * Turno cortado no meio do stream, cancelado, com erro ou timeout **libera o id** e o reenvio
208
+ * roda de novo — inclusive tools de efeito colateral que já rodaram. É best-effort (Redis do BE
209
+ * fora → sem dedupe, campo ainda aceito). O escopo é API key + `consumerKey` (não a sessão) e a
210
+ * comparação é só mensagem+anexos: **nunca use um valor fixo** — um workflow agendado com a
211
+ * mesma mensagem receberia a mesma resposta por 24 h; reusar o id noutra sessão dá erro
212
+ * `config` (`IDEMPOTENT_REPLAY_OTHER_SESSION`). O BE devolve o id em minúsculas. Vale sob
213
+ * qualquer papel de API key. Enviado como `client_message_id`; contra um BE que não o aceite
214
+ * (`accepted_fields` sem ele) o núcleo recusa o turno (`config`).
215
+ */
216
+ clientMessageId?: string;
187
217
  /**
188
218
  * Identidade da pessoa do outro lado (memória por pessoa, denylist, gate de engajamento). Só vale
189
219
  * sob API key **admin/owner**. Para continuar conversas que um webhook já gravava, use o MESMO
@@ -285,8 +315,23 @@ interface AgentResult {
285
315
  suggestions?: string[];
286
316
  iterations?: number;
287
317
  executionTimeMs?: number;
288
- /** `execution_id` do turno (evento `metrics`) — handle de correlação no console/telemetria. */
318
+ /**
319
+ * `execution_id` do turno — handle de correlação no console/telemetria. Lido do `metadata`
320
+ * (BE desde 17/10/2026) com fallback no `metrics`; ausente num turno barrado por gate.
321
+ */
289
322
  executionId?: string;
323
+ /** `client_message_id` que o BE aplicou ao turno (eco do que você mandou, em **minúsculas**). */
324
+ clientMessageId?: string;
325
+ /**
326
+ * `true` = esta é a resposta GRAVADA de um turno anterior com o mesmo `clientMessageId`
327
+ * (reenvio idempotente). O agente não rodou de novo: sem `usage`/`model`/`iterations`, e
328
+ * `truncated`/`finishReason` não vêm mesmo que o original fosse `LENGTH`.
329
+ */
330
+ idempotentReplay?: boolean;
331
+ /** Id da mensagem do usuário gravada na sessão (quando o BE o deu). */
332
+ userMessageId?: string;
333
+ /** Id da mensagem do agente gravada na sessão (quando o BE o deu). */
334
+ assistantMessageId?: string;
290
335
  /** Campos do pedido que o BE aplicou (`metadata.accepted_fields`). Ausente = backend antigo. */
291
336
  acceptedFields?: string[];
292
337
  /** Campos do pedido que o BE recebeu e ignorou (`metadata.ignored_fields`). */
@@ -301,8 +346,9 @@ interface AgentResult {
301
346
  * Desfecho do turno, lido do `done` (`exitReason` do motor). `undefined` = backend antigo ou
302
347
  * stream cortado antes do `done`. Só chegam aqui os desfechos cujo `content` é resposta
303
348
  * (`COMPLETED`, `LENGTH`), o `PAUSED` (aprovação pendente ou agente pausado) e desfechos novos.
304
- * `TIMEOUT`, `ERROR`, `CANCELLED`, `SUSPENDED` e os de bloqueio viram `ZihinAgentError` — o
305
- * `content` desses turnos é a mensagem de falha/recusa do motor (`errorFromDoneChunk`).
349
+ * `TIMEOUT`, `ERROR`, `CANCELLED`, `SUSPENDED`, `REFUSED`, `CONTEXT_LIMIT` e os de bloqueio
350
+ * viram `ZihinAgentError` — o `content` desses turnos é a mensagem de falha/recusa do motor
351
+ * (`errorFromDoneChunk`).
306
352
  */
307
353
  outcome?: AgentOutcome;
308
354
  /** `true` quando a resposta foi cortada pelo teto de saída do modelo (`done.truncated`). */
@@ -317,7 +363,11 @@ interface AgentResult {
317
363
  * contam como sucesso no BE (`isSuccessfulOutcome`); os demais valores são repassados como vêm.
318
364
  * Aberto (`string & {}`) porque o BE pode acrescentar desfechos (ex.: os de bloqueio).
319
365
  */
320
- type AgentOutcome = 'COMPLETED' | 'LENGTH' | 'TIMEOUT' | 'ERROR' | 'CANCELLED' | 'PAUSED' | 'SUSPENDED' | 'BUDGET_EXCEEDED' | 'QUOTA_EXCEEDED' | 'SESSION_BUSY' | 'CONFLICT' | (string & {});
366
+ type AgentOutcome = 'COMPLETED' | 'LENGTH' | 'TIMEOUT' | 'ERROR' | 'CANCELLED' | 'PAUSED' | 'SUSPENDED' | 'BUDGET_EXCEEDED' | 'QUOTA_EXCEEDED' | 'SESSION_BUSY' | 'CONFLICT'
367
+ /** O provedor do modelo recusou por política (desde 08/10/2026). O `content` é um texto neutro fixo. */
368
+ | 'REFUSED'
369
+ /** A conversa reuniu mais contexto do que o modelo processa (desde 14/10/2026). No `/stream` chega como `event: error`. */
370
+ | 'CONTEXT_LIMIT' | (string & {});
321
371
  /**
322
372
  * Qual relógio encerrou o turno (contrato de tempo §2): `turn` (prazo do turno, inclusive o
323
373
  * `options.timeoutMs` pedido), `first_signal` (o provedor não começou a responder), `idle`
@@ -341,6 +391,11 @@ type AgentStreamChunk =
341
391
  requestId?: string;
342
392
  agentId?: string;
343
393
  sessionId?: string;
394
+ /** `execution_id` do turno (BE desde 17/10/2026). Ausente num turno barrado por gate. */
395
+ executionId?: string;
396
+ clientMessageId?: string;
397
+ /** `true` = o BE vai repetir a resposta gravada (mesmo `clientMessageId`), sem rodar o agente. */
398
+ idempotentReplay?: boolean;
344
399
  acceptedFields?: string[];
345
400
  ignoredFields?: IgnoredField[];
346
401
  data: unknown;
@@ -371,6 +426,12 @@ type AgentStreamChunk =
371
426
  } | {
372
427
  type: 'action_proposed';
373
428
  data: unknown;
429
+ }
430
+ /** Aprovação humana encaminhada ao aprovador (`{ action_id, kind, title, expires_at }`); o turno fica `PAUSED`. */
431
+ | {
432
+ type: 'action_forwarded';
433
+ actionId?: string;
434
+ data: unknown;
374
435
  } | {
375
436
  type: 'token';
376
437
  content: string;
@@ -382,7 +443,9 @@ type AgentStreamChunk =
382
443
  }
383
444
  /**
384
445
  * O BE encerra o stream com `event: error` + `{ error: { type, code, message, request_id,
385
- * timed_out? } }` (`utils/sse.js`) — inclusive quando o TURNO morre no deadline. Antes isto
446
+ * timed_out? } }` (`utils/sse.js`) — inclusive quando o TURNO morre no deadline. O BE NÃO põe
447
+ * `status` nem `details` no fio (ficam no servidor): `SESSION_BUSY`, `IDEMPOTENCY_CONFLICT`,
448
+ * `CONTEXT_LIMIT`, `CONFLICT` e os de quota chegam só com `code`+`message`. Antes isto
386
449
  * caía em `unknown` e o `invokeAgent` ignorava: se já tinha chegado token, devolvia a
387
450
  * deliberação crua como SUCESSO.
388
451
  *
@@ -422,6 +485,11 @@ type AgentStreamChunk =
422
485
  timeoutClock?: TimeoutClock;
423
486
  truncated?: boolean;
424
487
  finishReason?: string;
488
+ executionId?: string;
489
+ clientMessageId?: string;
490
+ idempotentReplay?: boolean;
491
+ userMessageId?: string;
492
+ assistantMessageId?: string;
425
493
  data: unknown;
426
494
  } | {
427
495
  type: 'response';
@@ -436,6 +504,11 @@ type AgentStreamChunk =
436
504
  suggestions?: string[];
437
505
  /** Resposta ENLATADA de um gate (handoff humano, denylist, sessão suspensa) — não do agente. */
438
506
  gated?: boolean;
507
+ executionId?: string;
508
+ clientMessageId?: string;
509
+ idempotentReplay?: boolean;
510
+ userMessageId?: string;
511
+ assistantMessageId?: string;
439
512
  data: unknown;
440
513
  } | {
441
514
  type: 'metrics';
@@ -557,7 +630,7 @@ interface TriggerInfo {
557
630
  interface ListTriggersParams {
558
631
  /** Filtra por agente. */
559
632
  agentId?: string;
560
- /** Filtra por tipo (webhook | db_event | email | schedule). */
633
+ /** Filtra por tipo (`webhook` | `email` | `schedule`; `db_event` foi removido do BE em 03/10/2026). */
561
634
  triggerType?: string;
562
635
  /** Apenas habilitados. */
563
636
  enabledOnly?: boolean;
@@ -586,9 +659,19 @@ interface DispatchResult {
586
659
  accepted: boolean;
587
660
  triggerId?: string;
588
661
  agentId?: string;
662
+ /** Id da execução do TRIGGER (`trigger_executions`), não o `execution_id` do turno do agente. */
589
663
  executionId?: string;
590
664
  /** Presente quando o trigger é `sync`: resposta imediata do agente. */
591
665
  agentResponse?: string;
666
+ /**
667
+ * Desfecho do turno no modo `sync` (BE desde 14/10/2026; ausente = backend antigo). Só chegam
668
+ * aqui os desfechos cujo `agentResponse` é resposta (`COMPLETED`, `LENGTH`) e o `PAUSED`; os
669
+ * demais (`REFUSED`, `CONTEXT_LIMIT`, `TIMEOUT`, `ERROR`…) viram `ZihinAgentError` mesmo com
670
+ * `success: true` no corpo — o BE mantém `success` como "entregou", e o texto é a frase da
671
+ * plataforma, não uma resposta. Limite: o sync não traz `status.blocked`, então o `PAUSED` de
672
+ * agente pausado/arquivado chega aqui com o texto de recusa em `agentResponse`.
673
+ */
674
+ outcome?: AgentOutcome;
592
675
  usage?: Usage;
593
676
  /** Iterações do laço de tools no turno (sync). */
594
677
  iterations?: number;
@@ -868,15 +951,32 @@ declare function createClient(config: ZihinClientConfig): ZihinAgentClient;
868
951
  * O regex no nome só vale como FALLBACK para backends que ainda não emitem `timed_out`
869
952
  * (self-hosted/antigos). Some numa major futura, depois do rename no BE.
870
953
  */
871
- declare function classifyStreamErrorKind(code: string, timedOut?: boolean): 'timeout' | 'server' | 'config';
954
+ declare function classifyStreamErrorKind(code: string, timedOut?: boolean): ZihinErrorKind;
872
955
  /**
873
956
  * Converte o chunk `type: 'error'` (evento terminal do BE) num `ZihinAgentError` — a MESMA regra
874
957
  * que o `invokeAgent` aplica, exportada para quem consome `streamAgent` direto (o adapter do
875
958
  * Paperclip, por exemplo) não reimplementar a classificação por conta e divergir dela.
959
+ * `ctx` leva os ids já vistos no `metadata` (o `error` em si não os traz).
876
960
  */
877
961
  declare function errorFromStreamChunk(chunk: Extract<AgentStreamChunk, {
878
962
  type: 'error';
879
- }>): ZihinAgentError;
963
+ }>, ctx?: {
964
+ executionId?: string;
965
+ sessionId?: string;
966
+ }): ZihinAgentError;
967
+ /**
968
+ * Converte um `outcome` cru (fora do `/stream`: `outcome` do webhook sync, `exit_reason` de uma
969
+ * execução) num `ZihinAgentError`, ou `undefined` quando o texto que o acompanha É resposta
970
+ * (`COMPLETED`, `LENGTH`, `PAUSED`, desconhecido/ausente). Mesma tabela do `errorFromDoneChunk`,
971
+ * sem o contexto de `status.blocked` que só o SSE tem.
972
+ */
973
+ declare function errorFromOutcome(outcome: string | undefined, ctx?: {
974
+ executionId?: string;
975
+ sessionId?: string;
976
+ status?: number;
977
+ endpoint?: string;
978
+ attempts?: number;
979
+ }): ZihinAgentError | undefined;
880
980
  /**
881
981
  * Converte o `done` de um turno que NÃO produziu resposta num `ZihinAgentError`; devolve
882
982
  * `undefined` quando o `content` é resposta. Mesma regra do `invokeAgent`, exportada para quem
@@ -991,7 +1091,7 @@ interface SignedRequestHeaders {
991
1091
  *
992
1092
  * ⚠️ No build ESM depende de `process.getBuiltinModule` (Node >=20.16); em Node mais antigo via
993
1093
  * `import` lança `ZihinAgentError` com `kind: 'config'`. O build CJS funciona em qualquer Node
994
- * >=18. (Todo o resto do núcleo é Web-API puro e roda nos dois.)
1094
+ * suportado (>=20.16). (Todo o resto do núcleo é Web-API puro e roda nos dois.)
995
1095
  */
996
1096
  declare function signRequest(payload: string, secretKey: string, timestamp?: number): SignedRequestHeaders;
997
1097
 
@@ -1022,4 +1122,4 @@ declare function signRequest(payload: string, secretKey: string, timestamp?: num
1022
1122
  */
1023
1123
  declare const MAX_TASK_PAYLOAD_BYTES: number;
1024
1124
 
1025
- export { type AgentOutcome, type AgentResult, type AgentStreamChunk, type AgentSummary, type Attachment, type AttachmentType, type CancelTaskParams, type CancelTaskResult, type ClientBlockType, type ClientCapabilities, type ConsumerIdentity, type ContextValue, type CreateTriggerParams, DEADLINE_GRACE_MS, DEFAULT_LIMITS, type DelegateTaskParams, type DeleteTriggerParams, type DispatchAgentParams, type DispatchResult, type ExecutionStatus, type FindOrCreateResult, type GetExecutionParams, type GetTaskParams, type GetTriggerParams, type IgnoredField, type InvokeAgentParams, type InvokeOptions, type ListTasksParams, type ListTriggersParams, MAX_TASK_PAYLOAD_BYTES, OPERATION_TIMEOUT_MS, type QueryExtraction, type ResponseTransformMode, type RetryInfo, type SignedRequestHeaders, type Source, type TaskDossier, type TaskExecution, type TaskInfo, type TaskListPage, type TimeoutClock, type TimeoutOperation, type TriggerInfo, type TriggerMode, type TriggerRef, type UpdateTriggerParams, type Usage, ZihinAgentClient, ZihinAgentError, type ZihinClientConfig, type ZihinErrorKind, classifyStreamErrorKind, createClient, errorFromDoneChunk, errorFromStreamChunk, isCommercialBlock, sanitizeApiError, signRequest, validateApiKeyFormat, validateBaseUrl, validatePromptSize };
1125
+ export { type AgentOutcome, type AgentResult, type AgentStreamChunk, type AgentSummary, type Attachment, type AttachmentType, type CancelTaskParams, type CancelTaskResult, type ClientBlockType, type ClientCapabilities, type ConsumerIdentity, type ContextValue, type CreateTriggerParams, DEADLINE_GRACE_MS, DEFAULT_LIMITS, type DelegateTaskParams, type DeleteTriggerParams, type DispatchAgentParams, type DispatchResult, type ExecutionStatus, type FindOrCreateResult, type GetExecutionParams, type GetTaskParams, type GetTriggerParams, type IgnoredField, type InvokeAgentParams, type InvokeOptions, type ListTasksParams, type ListTriggersParams, MAX_TASK_PAYLOAD_BYTES, OPERATION_TIMEOUT_MS, type QueryExtraction, type ResponseTransformMode, type RetryInfo, type SignedRequestHeaders, type Source, type TaskDossier, type TaskExecution, type TaskInfo, type TaskListPage, type TimeoutClock, type TimeoutOperation, type TriggerInfo, type TriggerMode, type TriggerRef, type UpdateTriggerParams, type Usage, ZihinAgentClient, ZihinAgentError, type ZihinClientConfig, type ZihinErrorKind, classifyStreamErrorKind, createClient, errorFromDoneChunk, errorFromOutcome, errorFromStreamChunk, isCommercialBlock, sanitizeApiError, signRequest, validateApiKeyFormat, validateBaseUrl, validatePromptSize };