@zihin/agent-client 0.5.0 → 0.6.1
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 +98 -0
- package/dist/index.d.mts +111 -5
- package/dist/index.d.ts +111 -5
- package/dist/index.js +251 -4
- package/dist/index.mjs +251 -4
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -50,6 +50,104 @@ for await (const chunk of zihin.streamAgent({ agentId, message: 'Olá' })) {
|
|
|
50
50
|
}
|
|
51
51
|
```
|
|
52
52
|
|
|
53
|
+
### Contexto estruturado, identidade e sessão derivada (0.6)
|
|
54
|
+
|
|
55
|
+
> Backend: zihin-agent-builder#669 + #676, em produção desde 01/10/2026. Contra um backend
|
|
56
|
+
> anterior, o núcleo **recusa** o turno em vez de rodá-lo sem os campos (ver abaixo).
|
|
57
|
+
|
|
58
|
+
```ts
|
|
59
|
+
await zihin.invokeAgent({
|
|
60
|
+
agentId,
|
|
61
|
+
message: 'Qual o status do meu pedido?',
|
|
62
|
+
consumerKey: '5511999990000', // quem é a pessoa — só dígitos, como o webhook grava o telefone
|
|
63
|
+
context: { cliente: 'Ana', plano: 'pro' }, // o MODELO lê (bloco junto da mensagem do usuário)
|
|
64
|
+
bindings: { hashFornecedor: 'f-81c2' }, // só as TOOLS recebem; nunca vai ao prompt
|
|
65
|
+
sessionFrom: ['hashFornecedor'], // deriva o session_id destes campos
|
|
66
|
+
});
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
| Campo | Quem vê | Para quê |
|
|
70
|
+
|---|---|---|
|
|
71
|
+
| `context` | modelo + tools | dados que o agente deve considerar na resposta |
|
|
72
|
+
| `bindings` | só tools | ids internos, chaves de busca — nada que o modelo precise ler |
|
|
73
|
+
| `metadata` | ninguém no turno | rastreio opaco (`traceId`, `correlationId`) |
|
|
74
|
+
|
|
75
|
+
- **Credencial não vai em nenhum deles.** Use um secret do tenant.
|
|
76
|
+
- `context` **fica gravado** junto da mensagem (`declared_context`) e o bloco se repete em toda
|
|
77
|
+
mensagem do usuário, para o prompt cache ver os mesmos bytes a cada turno. O BE redige segredos
|
|
78
|
+
no `context` por nome de chave e por formato de valor, e avisa: `ignoredFields` traz
|
|
79
|
+
`{ field: 'context.<campo>', reason: 'redacted' }`. O campo segue aceito, com o valor alterado; o
|
|
80
|
+
núcleo **não** recusa o turno, só repassa o aviso. `bindings` **não é gravado nem redigido**: só
|
|
81
|
+
existe no contexto das tools durante o turno.
|
|
82
|
+
> Backends anteriores ao zihin-agent-builder#678 redigiam sem aviso e casavam `sk-…` dentro de
|
|
83
|
+
> valores comuns (`task-1234` → `ta[REDACTED]`). Contra eles, mande ids de tool e de sessão em
|
|
84
|
+
> `bindings`.
|
|
85
|
+
- `context` e `bindings` são **planos** (só string/number/boolean/null). Os tetos são os do BE,
|
|
86
|
+
checados no cliente antes de gastar turno: até 50 campos; nome `^[A-Za-z_][A-Za-z0-9_-]{0,63}$`;
|
|
87
|
+
valor de texto até 2.000 caracteres; total (JSON) até **2.000** no `context` e 16.000 no
|
|
88
|
+
`bindings`. `sessionFrom` aceita de 1 a 5 nomes.
|
|
89
|
+
- `context`, `bindings`, `sessionFrom` e `consumerKey` só valem sob API key **admin/owner**.
|
|
90
|
+
`metadata` vale com qualquer key.
|
|
91
|
+
- **Campo descartado não passa em silêncio**: se você mandou `context`/`bindings`/`sessionFrom` e o evento
|
|
92
|
+
`metadata` não trouxe `accepted_fields` (backend antigo) ou trouxe o campo em `ignored_fields`, o
|
|
93
|
+
núcleo lança `ZihinAgentError('config')` nomeando o campo e o motivo, e fecha o stream para
|
|
94
|
+
cancelar o turno. A resposta não é usada, mas a mensagem pode já estar gravada: o turno barrado
|
|
95
|
+
por gate (handoff, denylist) grava a troca mesmo recusado, então **reenviar depois de corrigir gera
|
|
96
|
+
duas linhas no histórico** da conversa. Motivos que recusam: `requires_admin_api_key` (JWT ou key
|
|
97
|
+
sem papel) e `reserved_key` (por chave, ex. `context.branch`: nome reservado do motor, renomeie).
|
|
98
|
+
`redacted` só avisa (ver acima). Todo campo
|
|
99
|
+
pedido precisa estar em `accepted_fields` (`consumer_key` também, quando o BE publica a lista).
|
|
100
|
+
Erro de validação do BE (`VALIDATION_ERROR` no stream) também sai como `config`. `AgentResult.acceptedFields`/`ignoredFields` mostram o que
|
|
101
|
+
o BE fez; `options.temperature`/`maxTokens` aparecem em `ignoredFields` sem bloquear (o `/stream`
|
|
102
|
+
não os aplica).
|
|
103
|
+
- `consumerKey` precisa ser o **mesmo valor** que o webhook gravava, para continuar as conversas dele.
|
|
104
|
+
|
|
105
|
+
**Migrando de webhook `derive`**: um trigger com `session: { derive: ['hashFornecedor'] }` e
|
|
106
|
+
payload `{ hashFornecedor: 'f-81c2', ... }` vira `sessionFrom: ['hashFornecedor']` com o valor em
|
|
107
|
+
`bindings` (ou em `context`, se o modelo deve lê-lo). O BE usa a mesma função de derivação, então o
|
|
108
|
+
mesmo valor cai no **mesmo** `session_id`. `sessionFrom` não combina com `sessionId`. Como no
|
|
109
|
+
webhook: basta **um** campo presente (`0`, `false` e `''` contam como ausentes), um conjunto parcial
|
|
110
|
+
gera **outra** sessão, e a ordem dos campos entra no hash. Chave repetida nos dois: vale o valor de
|
|
111
|
+
`bindings`. Campo do `sessionFrom` que a redação alterou (no `context`) é recusado com
|
|
112
|
+
`VALIDATION_ERROR` (sai como `config`): mande-o em `bindings`.
|
|
113
|
+
|
|
114
|
+
### Nome do contato e sugestões de resposta (0.6.1)
|
|
115
|
+
|
|
116
|
+
> Backend: zihin-agent-builder `1aa752e6` (+ migration `20261001000002`). Contra um backend
|
|
117
|
+
> anterior os dois recursos só não acontecem: o turno roda normalmente.
|
|
118
|
+
|
|
119
|
+
```ts
|
|
120
|
+
const r = await zihin.invokeAgent({
|
|
121
|
+
agentId,
|
|
122
|
+
message: 'Quero canetas azuis',
|
|
123
|
+
consumer: { key: 'dev-cotai-1410', name: 'Fernando Produtor' }, // nome aparece nos contatos do console
|
|
124
|
+
capabilities: { blocks: ['quick_replies'] }, // minha tela mostra sugestões
|
|
125
|
+
});
|
|
126
|
+
r.suggestions; // ['Caneta A', 'Caneta B'] — ou undefined quando o agente não sugeriu
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
- **`consumer: { key, name? }`** é o `consumerKey` com o nome junto. `consumer.key` e `consumerKey`
|
|
130
|
+
são o mesmo campo (mande um; se vierem os dois, têm de ser iguais) e seguem a mesma regra: só
|
|
131
|
+
valem sob API key admin/owner, e o núcleo recusa o turno se o servidor os ignorar. O nome vai como
|
|
132
|
+
`consumer_display_name` (até 255 caracteres) e vale **do presente em diante**: atualiza o
|
|
133
|
+
contato e a conversa em andamento; conversas encerradas guardam o nome da época (o histórico não
|
|
134
|
+
é reescrito). Dado pessoal: mande só o que a operação precisa ver.
|
|
135
|
+
- **Uma `consumer.key` por pessoa.** A chave é a identidade: memória, bloqueio e engajamento são
|
|
136
|
+
dela. Se várias pessoas usam o mesmo canal (ex.: um WhatsApp comercial que troca de vendedor),
|
|
137
|
+
dê a cada uma a sua chave — trocar só o `name` não separa as pessoas.
|
|
138
|
+
- **`capabilities: { blocks: ['quick_replies'] }`** diz que a sua tela renderiza sugestões de
|
|
139
|
+
resposta. Sem declarar, o agente nem recebe a ferramenta — nada é gerado e nada é cobrado. É um
|
|
140
|
+
**pedido**, não uma exigência: qualquer API key pode mandar, e um servidor que não o conheça só o
|
|
141
|
+
reporta em `ignoredFields` (o turno não falha). O dono do agente pode desligar as sugestões
|
|
142
|
+
(`quick_replies_enabled`), e o agente decide quando vale sugerir — nem todo turno traz.
|
|
143
|
+
- **`suggestions`** (em `AgentResult` e no chunk `response`) são os rótulos do bloco `quick_replies`
|
|
144
|
+
de `blocks`, já filtrados: até 4, curtos. Tocar uma sugestão = mandar o texto dela como a próxima
|
|
145
|
+
`message`. Num turno só com sugestões, `content` vem **vazio**.
|
|
146
|
+
- **Descontinuados**: `options.jitMode`, `options.temperature` e `options.maxTokens` não têm efeito no
|
|
147
|
+
`/stream` (temperatura e teto de saída são configuração do agente). Continuam sendo enviados e o
|
|
148
|
+
servidor os lista em `ignoredFields`; o núcleo emite um `ZihinDeprecationWarning` **uma vez por
|
|
149
|
+
processo** (`ZIHIN_DEP001`/`ZIHIN_DEP002`). Saem do SDK na 1.0.
|
|
150
|
+
|
|
53
151
|
## Uso — caminho assíncrono (triggers)
|
|
54
152
|
|
|
55
153
|
```ts
|
package/dist/index.d.mts
CHANGED
|
@@ -118,7 +118,17 @@ interface Attachment {
|
|
|
118
118
|
type: AttachmentType;
|
|
119
119
|
}
|
|
120
120
|
interface InvokeOptions {
|
|
121
|
+
/**
|
|
122
|
+
* @deprecated Sem efeito: o `/stream` usa a temperatura da configuração do agente
|
|
123
|
+
* (`llm_config.temperature`, no console ou via `update_agent`). O servidor ignora o campo e o
|
|
124
|
+
* reporta em `ignoredFields`. Será retirado do SDK na 1.0.
|
|
125
|
+
*/
|
|
121
126
|
temperature?: number;
|
|
127
|
+
/**
|
|
128
|
+
* @deprecated Sem efeito: o teto de saída é a configuração do agente (`llm_config.max_tokens`,
|
|
129
|
+
* no console ou via `update_agent`). O servidor ignora o campo e o reporta em `ignoredFields`.
|
|
130
|
+
* Será retirado do SDK na 1.0.
|
|
131
|
+
*/
|
|
122
132
|
maxTokens?: number;
|
|
123
133
|
/**
|
|
124
134
|
* Teto do TURNO desta chamada (ms); sobrepõe o do cliente. Vai ao BE como `options.timeout_ms`
|
|
@@ -130,9 +140,41 @@ interface InvokeOptions {
|
|
|
130
140
|
* local soma `DEADLINE_GRACE_MS` a este valor para ainda receber o desfecho do servidor.
|
|
131
141
|
*/
|
|
132
142
|
timeoutMs?: number;
|
|
133
|
-
/**
|
|
143
|
+
/**
|
|
144
|
+
* @deprecated Recurso descontinuado no servidor (ago/2026): sem efeito, reportado em
|
|
145
|
+
* `ignoredFields`. Remova-o. Será retirado do SDK na 1.0.
|
|
146
|
+
*/
|
|
134
147
|
jitMode?: boolean;
|
|
135
148
|
}
|
|
149
|
+
/** Blocos estruturados que a sua interface sabe renderizar (`capabilities.blocks`). */
|
|
150
|
+
type ClientBlockType = 'quick_replies';
|
|
151
|
+
/**
|
|
152
|
+
* O que a SUA tela sabe mostrar. O agente só oferece o que você declarar — sem declarar, nada é
|
|
153
|
+
* gerado e nada é cobrado. Não exige privilégio de API key.
|
|
154
|
+
*/
|
|
155
|
+
interface ClientCapabilities {
|
|
156
|
+
/**
|
|
157
|
+
* `quick_replies`: até 4 sugestões curtas de resposta ao fim do turno (o usuário toca e o texto
|
|
158
|
+
* vira a próxima mensagem). Chegam em `AgentResult.suggestions` (e no bloco `quick_replies` de
|
|
159
|
+
* `blocks`). O dono do agente pode desligá-las (`quick_replies_enabled`); o agente decide quando
|
|
160
|
+
* vale sugerir — nem todo turno traz sugestões.
|
|
161
|
+
*/
|
|
162
|
+
blocks?: ClientBlockType[];
|
|
163
|
+
}
|
|
164
|
+
/** A pessoa do outro lado da conversa. Só vale sob API key **admin/owner**. */
|
|
165
|
+
interface ConsumerIdentity {
|
|
166
|
+
/**
|
|
167
|
+
* Identificador estável da pessoa no SEU sistema (memória por pessoa, denylist, gate de
|
|
168
|
+
* engajamento). Para continuar conversas que um webhook já gravava, use o MESMO valor que o
|
|
169
|
+
* webhook usava. Enviado como `consumer_key`.
|
|
170
|
+
*/
|
|
171
|
+
key: string;
|
|
172
|
+
/**
|
|
173
|
+
* Nome para exibição no console da Zihin (lista de contatos). Enviado como
|
|
174
|
+
* `consumer_display_name`. Dado pessoal: mande só o que a operação precisa ver.
|
|
175
|
+
*/
|
|
176
|
+
name?: string;
|
|
177
|
+
}
|
|
136
178
|
interface InvokeAgentParams {
|
|
137
179
|
/** UUID do agente (path :agent_id). */
|
|
138
180
|
agentId: string;
|
|
@@ -140,8 +182,42 @@ interface InvokeAgentParams {
|
|
|
140
182
|
message?: string;
|
|
141
183
|
/** Anexos multimodais. `message` OU `attachments` é obrigatório. */
|
|
142
184
|
attachments?: Attachment[];
|
|
143
|
-
/** Continuidade de conversa (Supabase agent_sessions). */
|
|
185
|
+
/** Continuidade de conversa (Supabase agent_sessions). Não combina com `sessionFrom`. */
|
|
144
186
|
sessionId?: string;
|
|
187
|
+
/**
|
|
188
|
+
* Identidade da pessoa do outro lado (memória por pessoa, denylist, gate de engajamento). Só vale
|
|
189
|
+
* sob API key **admin/owner**. Para continuar conversas que um webhook já gravava, use o MESMO
|
|
190
|
+
* valor que o webhook usava. Enviado como `consumer_key`.
|
|
191
|
+
*/
|
|
192
|
+
consumerKey?: string;
|
|
193
|
+
/**
|
|
194
|
+
* A pessoa do outro lado: `{ key, name? }`. `consumer.key` é o mesmo que `consumerKey` (use um
|
|
195
|
+
* dos dois; se vierem os dois, têm de ser iguais). Só vale sob API key admin/owner.
|
|
196
|
+
*/
|
|
197
|
+
consumer?: ConsumerIdentity;
|
|
198
|
+
/** O que a sua interface sabe renderizar — ex.: `{ blocks: ['quick_replies'] }`. */
|
|
199
|
+
capabilities?: ClientCapabilities;
|
|
200
|
+
/**
|
|
201
|
+
* Contexto estruturado que o **modelo lê** — bloco de plataforma junto da mensagem do usuário
|
|
202
|
+
* (não no system prompt). **Fica gravado** com a mensagem (`declared_context`) e se repete em
|
|
203
|
+
* todo turno; o BE redige segredos por nome de chave E por formato de valor (ex.: `sk-…`), o
|
|
204
|
+
* que pode alterar valores legítimos — ids que vão a tools ou ao `sessionFrom` ficam melhor em
|
|
205
|
+
* `bindings`. Plano, só escalares. Tools também o recebem. Só vale sob key admin/owner.
|
|
206
|
+
* ⚠️ Credencial não vai aqui nem em `bindings`: use um secret do tenant.
|
|
207
|
+
*/
|
|
208
|
+
context?: Record<string, ContextValue>;
|
|
209
|
+
/**
|
|
210
|
+
* Valores que **só as tools** recebem — nunca vão ao prompt (ex.: id interno de fornecedor).
|
|
211
|
+
* Plano, só escalares; **nunca gravado** nem redigido (vive só no contexto das tools durante o
|
|
212
|
+
* turno). Só vale sob key admin/owner.
|
|
213
|
+
*/
|
|
214
|
+
bindings?: Record<string, ContextValue>;
|
|
215
|
+
/**
|
|
216
|
+
* Deriva o `session_id` dos valores destes campos de `context ∪ bindings`, com a MESMA função
|
|
217
|
+
* do webhook (`derive: [campo]`) — o mesmo valor cai na mesma sessão pelos dois caminhos. Não
|
|
218
|
+
* combina com `sessionId`. O id derivado volta em `AgentResult.sessionId`.
|
|
219
|
+
*/
|
|
220
|
+
sessionFrom?: string[];
|
|
145
221
|
options?: InvokeOptions;
|
|
146
222
|
metadata?: {
|
|
147
223
|
traceId?: string;
|
|
@@ -156,6 +232,15 @@ interface InvokeAgentParams {
|
|
|
156
232
|
*/
|
|
157
233
|
includeRaw?: boolean;
|
|
158
234
|
}
|
|
235
|
+
/** Valor aceito em `context`/`bindings`: plano e escalar (regra 8 do contrato de entrada). */
|
|
236
|
+
type ContextValue = string | number | boolean | null;
|
|
237
|
+
/** Campo que o BE recebeu e NÃO aplicou (evento `metadata.ignored_fields`). */
|
|
238
|
+
interface IgnoredField {
|
|
239
|
+
/** Nome no fio (`context`, `bindings`, `session_from`, `consumer_key`, `options.temperature`…). */
|
|
240
|
+
field: string;
|
|
241
|
+
/** Motivo reportado pelo BE (ex.: papel da key insuficiente), quando houver. */
|
|
242
|
+
reason?: string;
|
|
243
|
+
}
|
|
159
244
|
interface Usage {
|
|
160
245
|
inputTokens?: number;
|
|
161
246
|
outputTokens?: number;
|
|
@@ -192,10 +277,20 @@ interface AgentResult {
|
|
|
192
277
|
type: string;
|
|
193
278
|
[k: string]: unknown;
|
|
194
279
|
}>;
|
|
280
|
+
/**
|
|
281
|
+
* Sugestões de resposta do turno (bloco `quick_replies`), já extraídas de `blocks`. Só vêm se
|
|
282
|
+
* você declarou `capabilities: { blocks: ['quick_replies'] }`. O texto de cada uma é o que o
|
|
283
|
+
* usuário "diz" ao tocá-la. Num turno só com sugestões, `content` vem vazio.
|
|
284
|
+
*/
|
|
285
|
+
suggestions?: string[];
|
|
195
286
|
iterations?: number;
|
|
196
287
|
executionTimeMs?: number;
|
|
197
288
|
/** `execution_id` do turno (evento `metrics`) — handle de correlação no console/telemetria. */
|
|
198
289
|
executionId?: string;
|
|
290
|
+
/** Campos do pedido que o BE aplicou (`metadata.accepted_fields`). Ausente = backend antigo. */
|
|
291
|
+
acceptedFields?: string[];
|
|
292
|
+
/** Campos do pedido que o BE recebeu e ignorou (`metadata.ignored_fields`). */
|
|
293
|
+
ignoredFields?: IgnoredField[];
|
|
199
294
|
/**
|
|
200
295
|
* `true` = o `content` é a resposta ENLATADA de um gate da plataforma (conversa em atendimento
|
|
201
296
|
* humano, consumidor bloqueado, sessão suspensa), não do agente: nenhum modelo rodou. Em canal
|
|
@@ -234,11 +329,20 @@ type TimeoutClock = 'turn' | 'first_signal' | 'idle' | 'call' | (string & {});
|
|
|
234
329
|
* metadata, status, attachments, phase, thinking, tool, action_proposed, token,
|
|
235
330
|
* warning, response, metrics.
|
|
236
331
|
*/
|
|
237
|
-
type AgentStreamChunk =
|
|
332
|
+
type AgentStreamChunk =
|
|
333
|
+
/**
|
|
334
|
+
* Abertura do stream. `acceptedFields`/`ignoredFields` dizem o que o BE fez com cada campo do
|
|
335
|
+
* pedido (ausentes = backend anterior ao contrato de entrada). Se o chamador mandou
|
|
336
|
+
* `context`/`bindings`/`sessionFrom` e o BE não os aceitou, o `streamAgent` lança
|
|
337
|
+
* `ZihinAgentError('config')` ANTES de entregar este chunk — nunca segue em silêncio.
|
|
338
|
+
*/
|
|
339
|
+
{
|
|
238
340
|
type: 'metadata';
|
|
239
341
|
requestId?: string;
|
|
240
342
|
agentId?: string;
|
|
241
343
|
sessionId?: string;
|
|
344
|
+
acceptedFields?: string[];
|
|
345
|
+
ignoredFields?: IgnoredField[];
|
|
242
346
|
data: unknown;
|
|
243
347
|
}
|
|
244
348
|
/** `blocked` = motivo do bloqueio do turno (`agent_paused`, `budget_exceeded`…), quando houver. */
|
|
@@ -328,6 +432,8 @@ type AgentStreamChunk = {
|
|
|
328
432
|
type: string;
|
|
329
433
|
[k: string]: unknown;
|
|
330
434
|
}>;
|
|
435
|
+
/** Sugestões de resposta (bloco `quick_replies`), extraídas de `blocks`. */
|
|
436
|
+
suggestions?: string[];
|
|
331
437
|
/** Resposta ENLATADA de um gate (handoff humano, denylist, sessão suspensa) — não do agente. */
|
|
332
438
|
gated?: boolean;
|
|
333
439
|
data: unknown;
|
|
@@ -762,7 +868,7 @@ declare function createClient(config: ZihinClientConfig): ZihinAgentClient;
|
|
|
762
868
|
* O regex no nome só vale como FALLBACK para backends que ainda não emitem `timed_out`
|
|
763
869
|
* (self-hosted/antigos). Some numa major futura, depois do rename no BE.
|
|
764
870
|
*/
|
|
765
|
-
declare function classifyStreamErrorKind(code: string, timedOut?: boolean): 'timeout' | 'server';
|
|
871
|
+
declare function classifyStreamErrorKind(code: string, timedOut?: boolean): 'timeout' | 'server' | 'config';
|
|
766
872
|
/**
|
|
767
873
|
* Converte o chunk `type: 'error'` (evento terminal do BE) num `ZihinAgentError` — a MESMA regra
|
|
768
874
|
* que o `invokeAgent` aplica, exportada para quem consome `streamAgent` direto (o adapter do
|
|
@@ -916,4 +1022,4 @@ declare function signRequest(payload: string, secretKey: string, timestamp?: num
|
|
|
916
1022
|
*/
|
|
917
1023
|
declare const MAX_TASK_PAYLOAD_BYTES: number;
|
|
918
1024
|
|
|
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 };
|
|
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 };
|
package/dist/index.d.ts
CHANGED
|
@@ -118,7 +118,17 @@ interface Attachment {
|
|
|
118
118
|
type: AttachmentType;
|
|
119
119
|
}
|
|
120
120
|
interface InvokeOptions {
|
|
121
|
+
/**
|
|
122
|
+
* @deprecated Sem efeito: o `/stream` usa a temperatura da configuração do agente
|
|
123
|
+
* (`llm_config.temperature`, no console ou via `update_agent`). O servidor ignora o campo e o
|
|
124
|
+
* reporta em `ignoredFields`. Será retirado do SDK na 1.0.
|
|
125
|
+
*/
|
|
121
126
|
temperature?: number;
|
|
127
|
+
/**
|
|
128
|
+
* @deprecated Sem efeito: o teto de saída é a configuração do agente (`llm_config.max_tokens`,
|
|
129
|
+
* no console ou via `update_agent`). O servidor ignora o campo e o reporta em `ignoredFields`.
|
|
130
|
+
* Será retirado do SDK na 1.0.
|
|
131
|
+
*/
|
|
122
132
|
maxTokens?: number;
|
|
123
133
|
/**
|
|
124
134
|
* Teto do TURNO desta chamada (ms); sobrepõe o do cliente. Vai ao BE como `options.timeout_ms`
|
|
@@ -130,9 +140,41 @@ interface InvokeOptions {
|
|
|
130
140
|
* local soma `DEADLINE_GRACE_MS` a este valor para ainda receber o desfecho do servidor.
|
|
131
141
|
*/
|
|
132
142
|
timeoutMs?: number;
|
|
133
|
-
/**
|
|
143
|
+
/**
|
|
144
|
+
* @deprecated Recurso descontinuado no servidor (ago/2026): sem efeito, reportado em
|
|
145
|
+
* `ignoredFields`. Remova-o. Será retirado do SDK na 1.0.
|
|
146
|
+
*/
|
|
134
147
|
jitMode?: boolean;
|
|
135
148
|
}
|
|
149
|
+
/** Blocos estruturados que a sua interface sabe renderizar (`capabilities.blocks`). */
|
|
150
|
+
type ClientBlockType = 'quick_replies';
|
|
151
|
+
/**
|
|
152
|
+
* O que a SUA tela sabe mostrar. O agente só oferece o que você declarar — sem declarar, nada é
|
|
153
|
+
* gerado e nada é cobrado. Não exige privilégio de API key.
|
|
154
|
+
*/
|
|
155
|
+
interface ClientCapabilities {
|
|
156
|
+
/**
|
|
157
|
+
* `quick_replies`: até 4 sugestões curtas de resposta ao fim do turno (o usuário toca e o texto
|
|
158
|
+
* vira a próxima mensagem). Chegam em `AgentResult.suggestions` (e no bloco `quick_replies` de
|
|
159
|
+
* `blocks`). O dono do agente pode desligá-las (`quick_replies_enabled`); o agente decide quando
|
|
160
|
+
* vale sugerir — nem todo turno traz sugestões.
|
|
161
|
+
*/
|
|
162
|
+
blocks?: ClientBlockType[];
|
|
163
|
+
}
|
|
164
|
+
/** A pessoa do outro lado da conversa. Só vale sob API key **admin/owner**. */
|
|
165
|
+
interface ConsumerIdentity {
|
|
166
|
+
/**
|
|
167
|
+
* Identificador estável da pessoa no SEU sistema (memória por pessoa, denylist, gate de
|
|
168
|
+
* engajamento). Para continuar conversas que um webhook já gravava, use o MESMO valor que o
|
|
169
|
+
* webhook usava. Enviado como `consumer_key`.
|
|
170
|
+
*/
|
|
171
|
+
key: string;
|
|
172
|
+
/**
|
|
173
|
+
* Nome para exibição no console da Zihin (lista de contatos). Enviado como
|
|
174
|
+
* `consumer_display_name`. Dado pessoal: mande só o que a operação precisa ver.
|
|
175
|
+
*/
|
|
176
|
+
name?: string;
|
|
177
|
+
}
|
|
136
178
|
interface InvokeAgentParams {
|
|
137
179
|
/** UUID do agente (path :agent_id). */
|
|
138
180
|
agentId: string;
|
|
@@ -140,8 +182,42 @@ interface InvokeAgentParams {
|
|
|
140
182
|
message?: string;
|
|
141
183
|
/** Anexos multimodais. `message` OU `attachments` é obrigatório. */
|
|
142
184
|
attachments?: Attachment[];
|
|
143
|
-
/** Continuidade de conversa (Supabase agent_sessions). */
|
|
185
|
+
/** Continuidade de conversa (Supabase agent_sessions). Não combina com `sessionFrom`. */
|
|
144
186
|
sessionId?: string;
|
|
187
|
+
/**
|
|
188
|
+
* Identidade da pessoa do outro lado (memória por pessoa, denylist, gate de engajamento). Só vale
|
|
189
|
+
* sob API key **admin/owner**. Para continuar conversas que um webhook já gravava, use o MESMO
|
|
190
|
+
* valor que o webhook usava. Enviado como `consumer_key`.
|
|
191
|
+
*/
|
|
192
|
+
consumerKey?: string;
|
|
193
|
+
/**
|
|
194
|
+
* A pessoa do outro lado: `{ key, name? }`. `consumer.key` é o mesmo que `consumerKey` (use um
|
|
195
|
+
* dos dois; se vierem os dois, têm de ser iguais). Só vale sob API key admin/owner.
|
|
196
|
+
*/
|
|
197
|
+
consumer?: ConsumerIdentity;
|
|
198
|
+
/** O que a sua interface sabe renderizar — ex.: `{ blocks: ['quick_replies'] }`. */
|
|
199
|
+
capabilities?: ClientCapabilities;
|
|
200
|
+
/**
|
|
201
|
+
* Contexto estruturado que o **modelo lê** — bloco de plataforma junto da mensagem do usuário
|
|
202
|
+
* (não no system prompt). **Fica gravado** com a mensagem (`declared_context`) e se repete em
|
|
203
|
+
* todo turno; o BE redige segredos por nome de chave E por formato de valor (ex.: `sk-…`), o
|
|
204
|
+
* que pode alterar valores legítimos — ids que vão a tools ou ao `sessionFrom` ficam melhor em
|
|
205
|
+
* `bindings`. Plano, só escalares. Tools também o recebem. Só vale sob key admin/owner.
|
|
206
|
+
* ⚠️ Credencial não vai aqui nem em `bindings`: use um secret do tenant.
|
|
207
|
+
*/
|
|
208
|
+
context?: Record<string, ContextValue>;
|
|
209
|
+
/**
|
|
210
|
+
* Valores que **só as tools** recebem — nunca vão ao prompt (ex.: id interno de fornecedor).
|
|
211
|
+
* Plano, só escalares; **nunca gravado** nem redigido (vive só no contexto das tools durante o
|
|
212
|
+
* turno). Só vale sob key admin/owner.
|
|
213
|
+
*/
|
|
214
|
+
bindings?: Record<string, ContextValue>;
|
|
215
|
+
/**
|
|
216
|
+
* Deriva o `session_id` dos valores destes campos de `context ∪ bindings`, com a MESMA função
|
|
217
|
+
* do webhook (`derive: [campo]`) — o mesmo valor cai na mesma sessão pelos dois caminhos. Não
|
|
218
|
+
* combina com `sessionId`. O id derivado volta em `AgentResult.sessionId`.
|
|
219
|
+
*/
|
|
220
|
+
sessionFrom?: string[];
|
|
145
221
|
options?: InvokeOptions;
|
|
146
222
|
metadata?: {
|
|
147
223
|
traceId?: string;
|
|
@@ -156,6 +232,15 @@ interface InvokeAgentParams {
|
|
|
156
232
|
*/
|
|
157
233
|
includeRaw?: boolean;
|
|
158
234
|
}
|
|
235
|
+
/** Valor aceito em `context`/`bindings`: plano e escalar (regra 8 do contrato de entrada). */
|
|
236
|
+
type ContextValue = string | number | boolean | null;
|
|
237
|
+
/** Campo que o BE recebeu e NÃO aplicou (evento `metadata.ignored_fields`). */
|
|
238
|
+
interface IgnoredField {
|
|
239
|
+
/** Nome no fio (`context`, `bindings`, `session_from`, `consumer_key`, `options.temperature`…). */
|
|
240
|
+
field: string;
|
|
241
|
+
/** Motivo reportado pelo BE (ex.: papel da key insuficiente), quando houver. */
|
|
242
|
+
reason?: string;
|
|
243
|
+
}
|
|
159
244
|
interface Usage {
|
|
160
245
|
inputTokens?: number;
|
|
161
246
|
outputTokens?: number;
|
|
@@ -192,10 +277,20 @@ interface AgentResult {
|
|
|
192
277
|
type: string;
|
|
193
278
|
[k: string]: unknown;
|
|
194
279
|
}>;
|
|
280
|
+
/**
|
|
281
|
+
* Sugestões de resposta do turno (bloco `quick_replies`), já extraídas de `blocks`. Só vêm se
|
|
282
|
+
* você declarou `capabilities: { blocks: ['quick_replies'] }`. O texto de cada uma é o que o
|
|
283
|
+
* usuário "diz" ao tocá-la. Num turno só com sugestões, `content` vem vazio.
|
|
284
|
+
*/
|
|
285
|
+
suggestions?: string[];
|
|
195
286
|
iterations?: number;
|
|
196
287
|
executionTimeMs?: number;
|
|
197
288
|
/** `execution_id` do turno (evento `metrics`) — handle de correlação no console/telemetria. */
|
|
198
289
|
executionId?: string;
|
|
290
|
+
/** Campos do pedido que o BE aplicou (`metadata.accepted_fields`). Ausente = backend antigo. */
|
|
291
|
+
acceptedFields?: string[];
|
|
292
|
+
/** Campos do pedido que o BE recebeu e ignorou (`metadata.ignored_fields`). */
|
|
293
|
+
ignoredFields?: IgnoredField[];
|
|
199
294
|
/**
|
|
200
295
|
* `true` = o `content` é a resposta ENLATADA de um gate da plataforma (conversa em atendimento
|
|
201
296
|
* humano, consumidor bloqueado, sessão suspensa), não do agente: nenhum modelo rodou. Em canal
|
|
@@ -234,11 +329,20 @@ type TimeoutClock = 'turn' | 'first_signal' | 'idle' | 'call' | (string & {});
|
|
|
234
329
|
* metadata, status, attachments, phase, thinking, tool, action_proposed, token,
|
|
235
330
|
* warning, response, metrics.
|
|
236
331
|
*/
|
|
237
|
-
type AgentStreamChunk =
|
|
332
|
+
type AgentStreamChunk =
|
|
333
|
+
/**
|
|
334
|
+
* Abertura do stream. `acceptedFields`/`ignoredFields` dizem o que o BE fez com cada campo do
|
|
335
|
+
* pedido (ausentes = backend anterior ao contrato de entrada). Se o chamador mandou
|
|
336
|
+
* `context`/`bindings`/`sessionFrom` e o BE não os aceitou, o `streamAgent` lança
|
|
337
|
+
* `ZihinAgentError('config')` ANTES de entregar este chunk — nunca segue em silêncio.
|
|
338
|
+
*/
|
|
339
|
+
{
|
|
238
340
|
type: 'metadata';
|
|
239
341
|
requestId?: string;
|
|
240
342
|
agentId?: string;
|
|
241
343
|
sessionId?: string;
|
|
344
|
+
acceptedFields?: string[];
|
|
345
|
+
ignoredFields?: IgnoredField[];
|
|
242
346
|
data: unknown;
|
|
243
347
|
}
|
|
244
348
|
/** `blocked` = motivo do bloqueio do turno (`agent_paused`, `budget_exceeded`…), quando houver. */
|
|
@@ -328,6 +432,8 @@ type AgentStreamChunk = {
|
|
|
328
432
|
type: string;
|
|
329
433
|
[k: string]: unknown;
|
|
330
434
|
}>;
|
|
435
|
+
/** Sugestões de resposta (bloco `quick_replies`), extraídas de `blocks`. */
|
|
436
|
+
suggestions?: string[];
|
|
331
437
|
/** Resposta ENLATADA de um gate (handoff humano, denylist, sessão suspensa) — não do agente. */
|
|
332
438
|
gated?: boolean;
|
|
333
439
|
data: unknown;
|
|
@@ -762,7 +868,7 @@ declare function createClient(config: ZihinClientConfig): ZihinAgentClient;
|
|
|
762
868
|
* O regex no nome só vale como FALLBACK para backends que ainda não emitem `timed_out`
|
|
763
869
|
* (self-hosted/antigos). Some numa major futura, depois do rename no BE.
|
|
764
870
|
*/
|
|
765
|
-
declare function classifyStreamErrorKind(code: string, timedOut?: boolean): 'timeout' | 'server';
|
|
871
|
+
declare function classifyStreamErrorKind(code: string, timedOut?: boolean): 'timeout' | 'server' | 'config';
|
|
766
872
|
/**
|
|
767
873
|
* Converte o chunk `type: 'error'` (evento terminal do BE) num `ZihinAgentError` — a MESMA regra
|
|
768
874
|
* que o `invokeAgent` aplica, exportada para quem consome `streamAgent` direto (o adapter do
|
|
@@ -916,4 +1022,4 @@ declare function signRequest(payload: string, secretKey: string, timestamp?: num
|
|
|
916
1022
|
*/
|
|
917
1023
|
declare const MAX_TASK_PAYLOAD_BYTES: number;
|
|
918
1024
|
|
|
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 };
|
|
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 };
|
package/dist/index.js
CHANGED
|
@@ -89,6 +89,20 @@ var RUNNING_STATUSES = /* @__PURE__ */ new Set([
|
|
|
89
89
|
function isTerminalStatus(status) {
|
|
90
90
|
return !RUNNING_STATUSES.has(status.toLowerCase().replace(/-/g, "_"));
|
|
91
91
|
}
|
|
92
|
+
var _warnedDeprecations = /* @__PURE__ */ new Set();
|
|
93
|
+
function warnDeprecatedOnce(code, message) {
|
|
94
|
+
if (_warnedDeprecations.has(code)) return;
|
|
95
|
+
_warnedDeprecations.add(code);
|
|
96
|
+
try {
|
|
97
|
+
const proc = globalThis.process;
|
|
98
|
+
if (typeof proc?.emitWarning === "function") {
|
|
99
|
+
proc.emitWarning(message, { type: "ZihinDeprecationWarning", code });
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
console.warn(`[${code}] ${message}`);
|
|
103
|
+
} catch {
|
|
104
|
+
}
|
|
105
|
+
}
|
|
92
106
|
|
|
93
107
|
// src/security.ts
|
|
94
108
|
var PRIVATE_IP_PATTERNS = [
|
|
@@ -910,6 +924,29 @@ async function* parseSse(response, opts = {}) {
|
|
|
910
924
|
}
|
|
911
925
|
|
|
912
926
|
// src/invoke.ts
|
|
927
|
+
function parseAcceptedFields(v) {
|
|
928
|
+
if (!Array.isArray(v)) return void 0;
|
|
929
|
+
return v.map((x) => safeId(typeof x === "string" ? x : str(rec(x).field) ?? str(rec(x).name))).filter((x) => !!x);
|
|
930
|
+
}
|
|
931
|
+
function parseIgnoredFields(v) {
|
|
932
|
+
if (!Array.isArray(v)) return void 0;
|
|
933
|
+
const out = [];
|
|
934
|
+
for (const x of v) {
|
|
935
|
+
const field = safeId(typeof x === "string" ? x : str(rec(x).field) ?? str(rec(x).name));
|
|
936
|
+
if (field) out.push(typeof x === "string" ? { field } : { field, reason: safeLabel(rec(x).reason) });
|
|
937
|
+
}
|
|
938
|
+
return out;
|
|
939
|
+
}
|
|
940
|
+
var MAX_SUGGESTIONS = 10;
|
|
941
|
+
var MAX_SUGGESTION_CHARS = 200;
|
|
942
|
+
function suggestionsFromBlocks(blocks) {
|
|
943
|
+
if (!Array.isArray(blocks)) return void 0;
|
|
944
|
+
const qr = [...blocks].reverse().find((b) => rec(b).type === "quick_replies");
|
|
945
|
+
const labels = rec(rec(qr).data).labels;
|
|
946
|
+
if (!Array.isArray(labels)) return void 0;
|
|
947
|
+
const out = labels.filter((l) => typeof l === "string" && l.trim().length > 0 && l.length <= MAX_SUGGESTION_CHARS).slice(0, MAX_SUGGESTIONS);
|
|
948
|
+
return out.length ? out : void 0;
|
|
949
|
+
}
|
|
913
950
|
function mapEvent(e) {
|
|
914
951
|
const d = rec(e.data);
|
|
915
952
|
switch (e.event) {
|
|
@@ -919,6 +956,8 @@ function mapEvent(e) {
|
|
|
919
956
|
requestId: safeId(d.request_id),
|
|
920
957
|
agentId: str(d.agent_id),
|
|
921
958
|
sessionId: safeId(d.session_id),
|
|
959
|
+
acceptedFields: parseAcceptedFields(d.accepted_fields),
|
|
960
|
+
ignoredFields: parseIgnoredFields(d.ignored_fields),
|
|
922
961
|
data: e.data
|
|
923
962
|
};
|
|
924
963
|
case "status":
|
|
@@ -974,6 +1013,7 @@ function mapEvent(e) {
|
|
|
974
1013
|
gated: d.gated === true ? true : void 0,
|
|
975
1014
|
sources: Array.isArray(d.sources) ? d.sources : void 0,
|
|
976
1015
|
blocks: Array.isArray(d.blocks) ? d.blocks : void 0,
|
|
1016
|
+
suggestions: suggestionsFromBlocks(d.blocks),
|
|
977
1017
|
data: e.data
|
|
978
1018
|
};
|
|
979
1019
|
case "metrics": {
|
|
@@ -996,6 +1036,12 @@ function buildRequestBody(params, normalizedTimeoutMs) {
|
|
|
996
1036
|
const body = {};
|
|
997
1037
|
if (params.message !== void 0) body.message = params.message;
|
|
998
1038
|
if (params.sessionId) body.session_id = params.sessionId;
|
|
1039
|
+
if (params.consumerKey) body.consumer_key = params.consumerKey;
|
|
1040
|
+
if (params.consumer?.name !== void 0) body.consumer_display_name = params.consumer.name;
|
|
1041
|
+
if (params.capabilities?.blocks?.length) body.capabilities = { blocks: [...params.capabilities.blocks] };
|
|
1042
|
+
if (params.context) body.context = { ...params.context };
|
|
1043
|
+
if (params.bindings) body.bindings = { ...params.bindings };
|
|
1044
|
+
if (params.sessionFrom?.length) body.session_from = [...params.sessionFrom];
|
|
999
1045
|
if (params.attachments?.length) {
|
|
1000
1046
|
body.attachments = params.attachments.map((a) => ({ url: a.url, type: a.type }));
|
|
1001
1047
|
}
|
|
@@ -1034,6 +1080,8 @@ function validateParams(params) {
|
|
|
1034
1080
|
throw new ZihinAgentError("config", sizeCheck.error || "Message too large.");
|
|
1035
1081
|
}
|
|
1036
1082
|
}
|
|
1083
|
+
validateStructuredInput(params);
|
|
1084
|
+
warnDeprecatedOptions(params);
|
|
1037
1085
|
if (params.options?.timeoutMs !== void 0) {
|
|
1038
1086
|
normalizeTimeoutMs(params.options.timeoutMs, "options.timeoutMs");
|
|
1039
1087
|
}
|
|
@@ -1044,12 +1092,193 @@ function validateParams(params) {
|
|
|
1044
1092
|
}
|
|
1045
1093
|
}
|
|
1046
1094
|
}
|
|
1047
|
-
|
|
1095
|
+
function warnDeprecatedOptions(params) {
|
|
1096
|
+
const o = params.options;
|
|
1097
|
+
if (!o) return;
|
|
1098
|
+
if (o.jitMode !== void 0) {
|
|
1099
|
+
warnDeprecatedOnce(
|
|
1100
|
+
"ZIHIN_DEP001",
|
|
1101
|
+
"`options.jitMode` is deprecated and has no effect (the server discontinued it). Remove it; it will be dropped from the SDK in 1.0."
|
|
1102
|
+
);
|
|
1103
|
+
}
|
|
1104
|
+
if (o.temperature !== void 0 || o.maxTokens !== void 0) {
|
|
1105
|
+
warnDeprecatedOnce(
|
|
1106
|
+
"ZIHIN_DEP002",
|
|
1107
|
+
"`options.temperature`/`options.maxTokens` have no effect: the agent's own configuration (`llm_config`) decides them \u2014 change it in the Zihin console or with `update_agent`. They will be dropped from the SDK in 1.0."
|
|
1108
|
+
);
|
|
1109
|
+
}
|
|
1110
|
+
}
|
|
1111
|
+
var MAX_FIELDS = 50;
|
|
1112
|
+
var MAX_VALUE_CHARS = 2e3;
|
|
1113
|
+
var MAX_TOTAL_CHARS = { context: 2e3, bindings: 16e3 };
|
|
1114
|
+
var MAX_SESSION_FROM_FIELDS = 5;
|
|
1115
|
+
var FIELD_NAME_RE = /^[A-Za-z_][A-Za-z0-9_-]{0,63}$/;
|
|
1116
|
+
var PROTOTYPE_KEYS = /* @__PURE__ */ new Set(["__proto__", "constructor", "prototype"]);
|
|
1117
|
+
var MAX_CONSUMER_KEY_CHARS = 200;
|
|
1118
|
+
var MAX_CONSUMER_NAME_CHARS = 255;
|
|
1119
|
+
var MAX_CLIENT_BLOCKS = 10;
|
|
1120
|
+
function snapshotStructuredInput(params) {
|
|
1121
|
+
const snap = (v) => isPlainObject(v) ? Object.fromEntries(Object.entries(v).filter(([, x]) => x !== void 0)) : v;
|
|
1122
|
+
return {
|
|
1123
|
+
...params,
|
|
1124
|
+
...normalizeConsumer(params),
|
|
1125
|
+
context: snap(params.context),
|
|
1126
|
+
bindings: snap(params.bindings),
|
|
1127
|
+
sessionFrom: Array.isArray(params.sessionFrom) ? [...params.sessionFrom] : params.sessionFrom,
|
|
1128
|
+
capabilities: isPlainObject(params.capabilities) ? {
|
|
1129
|
+
...params.capabilities,
|
|
1130
|
+
blocks: Array.isArray(params.capabilities.blocks) ? [...params.capabilities.blocks] : params.capabilities.blocks
|
|
1131
|
+
} : params.capabilities
|
|
1132
|
+
};
|
|
1133
|
+
}
|
|
1134
|
+
function normalizeConsumer(params) {
|
|
1135
|
+
const c = params.consumer;
|
|
1136
|
+
if (c === void 0) return {};
|
|
1137
|
+
if (!isPlainObject(c)) {
|
|
1138
|
+
throw new ZihinAgentError("config", "`consumer` must be an object: `{ key, name? }`.");
|
|
1139
|
+
}
|
|
1140
|
+
const key = c.key;
|
|
1141
|
+
if (params.consumerKey !== void 0 && key !== void 0 && params.consumerKey !== key) {
|
|
1142
|
+
throw new ZihinAgentError("config", "`consumerKey` and `consumer.key` differ \u2014 send only one of them.");
|
|
1143
|
+
}
|
|
1144
|
+
const resolved = key ?? params.consumerKey;
|
|
1145
|
+
return {
|
|
1146
|
+
consumerKey: resolved,
|
|
1147
|
+
consumer: { key: resolved, ...c.name !== void 0 ? { name: c.name } : {} }
|
|
1148
|
+
};
|
|
1149
|
+
}
|
|
1150
|
+
function isPlainObject(v) {
|
|
1151
|
+
if (typeof v !== "object" || v === null || Array.isArray(v)) return false;
|
|
1152
|
+
const proto = Object.getPrototypeOf(v);
|
|
1153
|
+
return proto === Object.prototype || proto === null;
|
|
1154
|
+
}
|
|
1155
|
+
function isScalar(v) {
|
|
1156
|
+
return v === null || typeof v === "string" || typeof v === "boolean" || typeof v === "number" && Number.isFinite(v);
|
|
1157
|
+
}
|
|
1158
|
+
function validateStructuredInput(params) {
|
|
1159
|
+
if (params.consumerKey !== void 0 && (typeof params.consumerKey !== "string" || !params.consumerKey.trim())) {
|
|
1160
|
+
throw new ZihinAgentError("config", "`consumerKey` must be a non-empty string.");
|
|
1161
|
+
}
|
|
1162
|
+
if (typeof params.consumerKey === "string" && params.consumerKey.length > MAX_CONSUMER_KEY_CHARS) {
|
|
1163
|
+
throw new ZihinAgentError("config", `\`consumerKey\` is longer than ${MAX_CONSUMER_KEY_CHARS} characters.`);
|
|
1164
|
+
}
|
|
1165
|
+
if (params.consumer !== void 0) {
|
|
1166
|
+
if (params.consumerKey === void 0) {
|
|
1167
|
+
throw new ZihinAgentError("config", "`consumer.key` is required (the name has nothing to attach to without it).");
|
|
1168
|
+
}
|
|
1169
|
+
const n = params.consumer.name;
|
|
1170
|
+
if (n !== void 0 && (typeof n !== "string" || !n.trim() || n.length > MAX_CONSUMER_NAME_CHARS)) {
|
|
1171
|
+
throw new ZihinAgentError("config", `\`consumer.name\` must be a non-empty string of up to ${MAX_CONSUMER_NAME_CHARS} characters.`);
|
|
1172
|
+
}
|
|
1173
|
+
}
|
|
1174
|
+
if (params.capabilities !== void 0) {
|
|
1175
|
+
const blocks = isPlainObject(params.capabilities) ? params.capabilities.blocks : null;
|
|
1176
|
+
if (blocks === null || blocks !== void 0 && (!Array.isArray(blocks) || blocks.length > MAX_CLIENT_BLOCKS || blocks.some((b) => typeof b !== "string"))) {
|
|
1177
|
+
throw new ZihinAgentError(
|
|
1178
|
+
"config",
|
|
1179
|
+
`\`capabilities\` must be \`{ blocks: [...] }\` with up to ${MAX_CLIENT_BLOCKS} block names (e.g. \`['quick_replies']\`).`
|
|
1180
|
+
);
|
|
1181
|
+
}
|
|
1182
|
+
}
|
|
1183
|
+
for (const name of ["context", "bindings"]) {
|
|
1184
|
+
const v = params[name];
|
|
1185
|
+
if (v === void 0) continue;
|
|
1186
|
+
if (!isPlainObject(v)) {
|
|
1187
|
+
throw new ZihinAgentError("config", `\`${name}\` must be a flat plain object.`);
|
|
1188
|
+
}
|
|
1189
|
+
const entries = Object.entries(v);
|
|
1190
|
+
if (entries.length > MAX_FIELDS) {
|
|
1191
|
+
throw new ZihinAgentError("config", `\`${name}\` accepts at most ${MAX_FIELDS} fields.`);
|
|
1192
|
+
}
|
|
1193
|
+
for (const [k, val] of entries) {
|
|
1194
|
+
if (!FIELD_NAME_RE.test(k) || PROTOTYPE_KEYS.has(k)) {
|
|
1195
|
+
throw new ZihinAgentError(
|
|
1196
|
+
"config",
|
|
1197
|
+
`Invalid field name in \`${name}\`: use letters, digits, \`_\` or \`-\`, starting with a letter or \`_\` (max 64 characters; \`constructor\`, \`prototype\` and \`__proto__\` are not allowed).`
|
|
1198
|
+
);
|
|
1199
|
+
}
|
|
1200
|
+
if (typeof val === "string" && val.length > MAX_VALUE_CHARS) {
|
|
1201
|
+
throw new ZihinAgentError("config", `\`${name}.${k}\` is longer than ${MAX_VALUE_CHARS} characters.`);
|
|
1202
|
+
}
|
|
1203
|
+
if (!isScalar(val)) {
|
|
1204
|
+
throw new ZihinAgentError(
|
|
1205
|
+
"config",
|
|
1206
|
+
`\`${name}.${k}\` must be a string, number, boolean or null \u2014 \`${name}\` is flat (no nested objects or arrays).`
|
|
1207
|
+
);
|
|
1208
|
+
}
|
|
1209
|
+
}
|
|
1210
|
+
if (JSON.stringify(v).length > MAX_TOTAL_CHARS[name]) {
|
|
1211
|
+
throw new ZihinAgentError("config", `\`${name}\` is longer than ${MAX_TOTAL_CHARS[name]} characters in total (as JSON).`);
|
|
1212
|
+
}
|
|
1213
|
+
}
|
|
1214
|
+
if (params.sessionFrom !== void 0) {
|
|
1215
|
+
if (!Array.isArray(params.sessionFrom) || params.sessionFrom.length > MAX_SESSION_FROM_FIELDS || params.sessionFrom.some((f) => typeof f !== "string" || !FIELD_NAME_RE.test(f) || PROTOTYPE_KEYS.has(f))) {
|
|
1216
|
+
throw new ZihinAgentError("config", `\`sessionFrom\` must be an array of up to ${MAX_SESSION_FROM_FIELDS} field names.`);
|
|
1217
|
+
}
|
|
1218
|
+
if (params.sessionFrom.length && params.sessionId) {
|
|
1219
|
+
throw new ZihinAgentError("config", "Use either `sessionId` or `sessionFrom`, not both.");
|
|
1220
|
+
}
|
|
1221
|
+
const available = { ...params.context, ...params.bindings };
|
|
1222
|
+
const has = (f) => Object.prototype.hasOwnProperty.call(available, f) && Boolean(available[f]);
|
|
1223
|
+
if (params.sessionFrom.length && !params.sessionFrom.some(has)) {
|
|
1224
|
+
throw new ZihinAgentError(
|
|
1225
|
+
"config",
|
|
1226
|
+
`\`sessionFrom\` names ${params.sessionFrom.map((f) => `"${f}"`).join(", ")}, but none of them has a value in \`context\` or \`bindings\`.`
|
|
1227
|
+
);
|
|
1228
|
+
}
|
|
1229
|
+
}
|
|
1230
|
+
}
|
|
1231
|
+
function requestedInputFields(params) {
|
|
1232
|
+
const out = [];
|
|
1233
|
+
if (params.context) out.push("context");
|
|
1234
|
+
if (params.bindings) out.push("bindings");
|
|
1235
|
+
if (params.sessionFrom?.length) out.push("session_from");
|
|
1236
|
+
return out;
|
|
1237
|
+
}
|
|
1238
|
+
function checkInputCapability(params, chunk) {
|
|
1239
|
+
const requested = requestedInputFields(params);
|
|
1240
|
+
const ignored = chunk?.ignoredFields ?? [];
|
|
1241
|
+
const sent = [...requested, ...params.consumerKey ? ["consumer_key"] : []];
|
|
1242
|
+
const blocking = ignored.filter(
|
|
1243
|
+
(f) => f.reason !== "redacted" && sent.some((name) => f.field === name || f.field.startsWith(`${name}.`))
|
|
1244
|
+
);
|
|
1245
|
+
const tail = "the stream was closed to cancel the turn, but the message may already be recorded in the session.";
|
|
1246
|
+
if (blocking.length) {
|
|
1247
|
+
const shown = blocking.slice(0, 5);
|
|
1248
|
+
const more = blocking.length > shown.length ? ` (+${blocking.length - shown.length} more)` : "";
|
|
1249
|
+
const what = shown.map((f) => f.reason ? `\`${f.field}\` (${f.reason})` : `\`${f.field}\``).join(", ") + more;
|
|
1250
|
+
const hint = blocking.some((f) => f.reason === "reserved_key") ? " Reserved keys are names the agent engine uses internally; rename them." : blocking.some((f) => f.reason === "requires_admin_api_key") ? " These fields only apply with an admin/owner API key." : "";
|
|
1251
|
+
throw new ZihinAgentError("config", `The server ignored ${what}.${hint} The answer is not used: ${tail}`, {
|
|
1252
|
+
requestId: chunk?.requestId
|
|
1253
|
+
});
|
|
1254
|
+
}
|
|
1255
|
+
if (requested.length && chunk?.acceptedFields === void 0) {
|
|
1256
|
+
throw new ZihinAgentError(
|
|
1257
|
+
"config",
|
|
1258
|
+
`This Zihin server does not support ${requested.map((f) => `\`${f}\``).join(", ")} on /stream yet (no \`accepted_fields\` in the stream metadata) \u2014 ${tail}`,
|
|
1259
|
+
{ requestId: chunk?.requestId }
|
|
1260
|
+
);
|
|
1261
|
+
}
|
|
1262
|
+
const mustAccept = chunk?.acceptedFields !== void 0 && params.consumerKey ? [...requested, "consumer_key"] : requested;
|
|
1263
|
+
const notAccepted = mustAccept.filter((f) => !chunk?.acceptedFields?.includes(f));
|
|
1264
|
+
if (notAccepted.length) {
|
|
1265
|
+
throw new ZihinAgentError(
|
|
1266
|
+
"config",
|
|
1267
|
+
`The server did not accept ${notAccepted.map((f) => `\`${f}\``).join(", ")} (missing from \`accepted_fields\`) \u2014 ${tail}`,
|
|
1268
|
+
{ requestId: chunk?.requestId }
|
|
1269
|
+
);
|
|
1270
|
+
}
|
|
1271
|
+
}
|
|
1272
|
+
var TURN_EVENTS = /* @__PURE__ */ new Set(["phase", "thinking", "tool", "action_proposed", "token", "response", "metrics", "done"]);
|
|
1273
|
+
async function* streamAgent(cfg, rawParams) {
|
|
1274
|
+
const params = snapshotStructuredInput(rawParams);
|
|
1048
1275
|
validateParams(params);
|
|
1049
1276
|
const callerTimeoutMs = params.options?.timeoutMs !== void 0 ? normalizeTimeoutMs(params.options.timeoutMs, "options.timeoutMs") : void 0;
|
|
1050
1277
|
const timeoutMs = callerTimeoutMs !== void 0 ? callerTimeoutMs + DEADLINE_GRACE_MS : resolveTimeout(cfg, "stream");
|
|
1051
1278
|
const reportedMs = callerTimeoutMs ?? timeoutMs;
|
|
1052
1279
|
const url = `${cfg.baseUrl}/api/v2/agents/${encodeURIComponent(params.agentId)}/stream`;
|
|
1280
|
+
let needsCapability = requestedInputFields(params).length > 0 || !!params.consumerKey;
|
|
1281
|
+
let gated = false;
|
|
1053
1282
|
const { response, deadline, endpoint } = await openStream({
|
|
1054
1283
|
url,
|
|
1055
1284
|
headers: buildHeaders(cfg.apiKey),
|
|
@@ -1077,7 +1306,14 @@ async function* streamAgent(cfg, params) {
|
|
|
1077
1306
|
maxEventBytes: cfg.maxStreamEventBytes,
|
|
1078
1307
|
maxTotalBytes: cfg.maxStreamTotalBytes
|
|
1079
1308
|
})) {
|
|
1080
|
-
|
|
1309
|
+
const chunk = mapEvent(e);
|
|
1310
|
+
if (needsCapability) {
|
|
1311
|
+
if (chunk.type === "metadata") checkInputCapability(params, chunk);
|
|
1312
|
+
else if (chunk.type === "status" && chunk.phase === "GATED") gated = true;
|
|
1313
|
+
else if (TURN_EVENTS.has(chunk.type) && !gated) checkInputCapability(params, void 0);
|
|
1314
|
+
if (chunk.type === "metadata" || TURN_EVENTS.has(chunk.type) && !gated) needsCapability = false;
|
|
1315
|
+
}
|
|
1316
|
+
yield chunk;
|
|
1081
1317
|
}
|
|
1082
1318
|
} finally {
|
|
1083
1319
|
deadline.dispose();
|
|
@@ -1085,6 +1321,7 @@ async function* streamAgent(cfg, params) {
|
|
|
1085
1321
|
}
|
|
1086
1322
|
function classifyStreamErrorKind(code, timedOut) {
|
|
1087
1323
|
if (timedOut === true) return "timeout";
|
|
1324
|
+
if (code === "VALIDATION_ERROR") return "config";
|
|
1088
1325
|
if (timedOut === false) return "server";
|
|
1089
1326
|
return /TIMEOUT/i.test(code) ? "timeout" : "server";
|
|
1090
1327
|
}
|
|
@@ -1177,6 +1414,8 @@ async function invokeAgent(cfg, params) {
|
|
|
1177
1414
|
let mintedSessionId;
|
|
1178
1415
|
let doneChunk;
|
|
1179
1416
|
let blocked;
|
|
1417
|
+
let acceptedFields;
|
|
1418
|
+
let ignoredFields;
|
|
1180
1419
|
let tokenBuffer = "";
|
|
1181
1420
|
for await (const chunk of streamAgent(cfg, params)) {
|
|
1182
1421
|
if (keepRaw) raw.push(chunk);
|
|
@@ -1186,8 +1425,11 @@ async function invokeAgent(cfg, params) {
|
|
|
1186
1425
|
if (chunk.type === "response") responseChunk = chunk;
|
|
1187
1426
|
else if (chunk.type === "metrics") metricsChunk = chunk;
|
|
1188
1427
|
else if (chunk.type === "token") tokenBuffer += chunk.content;
|
|
1189
|
-
else if (chunk.type === "metadata"
|
|
1190
|
-
|
|
1428
|
+
else if (chunk.type === "metadata") {
|
|
1429
|
+
if (chunk.sessionId) metadataSessionId = chunk.sessionId;
|
|
1430
|
+
acceptedFields = chunk.acceptedFields;
|
|
1431
|
+
ignoredFields = chunk.ignoredFields;
|
|
1432
|
+
} else if (chunk.type === "session" && chunk.sessionId) mintedSessionId = chunk.sessionId;
|
|
1191
1433
|
else if (chunk.type === "done") doneChunk = chunk;
|
|
1192
1434
|
else if (chunk.type === "status" && chunk.blocked) blocked = chunk.blocked;
|
|
1193
1435
|
}
|
|
@@ -1210,6 +1452,8 @@ async function invokeAgent(cfg, params) {
|
|
|
1210
1452
|
iterations: metricsChunk?.iterations,
|
|
1211
1453
|
executionTimeMs: metricsChunk?.executionTimeMs,
|
|
1212
1454
|
executionId,
|
|
1455
|
+
acceptedFields,
|
|
1456
|
+
ignoredFields,
|
|
1213
1457
|
raw
|
|
1214
1458
|
};
|
|
1215
1459
|
}
|
|
@@ -1223,6 +1467,7 @@ async function invokeAgent(cfg, params) {
|
|
|
1223
1467
|
usage: metricsChunk?.usage,
|
|
1224
1468
|
sources: responseChunk.sources,
|
|
1225
1469
|
blocks: responseChunk.blocks,
|
|
1470
|
+
suggestions: responseChunk.suggestions,
|
|
1226
1471
|
gated: responseChunk.gated,
|
|
1227
1472
|
iterations: metricsChunk?.iterations,
|
|
1228
1473
|
executionTimeMs: metricsChunk?.executionTimeMs,
|
|
@@ -1230,6 +1475,8 @@ async function invokeAgent(cfg, params) {
|
|
|
1230
1475
|
outcome: doneChunk?.outcome,
|
|
1231
1476
|
truncated: doneChunk?.truncated,
|
|
1232
1477
|
finishReason: doneChunk?.finishReason,
|
|
1478
|
+
acceptedFields,
|
|
1479
|
+
ignoredFields,
|
|
1233
1480
|
raw
|
|
1234
1481
|
};
|
|
1235
1482
|
}
|
package/dist/index.mjs
CHANGED
|
@@ -55,6 +55,20 @@ var RUNNING_STATUSES = /* @__PURE__ */ new Set([
|
|
|
55
55
|
function isTerminalStatus(status) {
|
|
56
56
|
return !RUNNING_STATUSES.has(status.toLowerCase().replace(/-/g, "_"));
|
|
57
57
|
}
|
|
58
|
+
var _warnedDeprecations = /* @__PURE__ */ new Set();
|
|
59
|
+
function warnDeprecatedOnce(code, message) {
|
|
60
|
+
if (_warnedDeprecations.has(code)) return;
|
|
61
|
+
_warnedDeprecations.add(code);
|
|
62
|
+
try {
|
|
63
|
+
const proc = globalThis.process;
|
|
64
|
+
if (typeof proc?.emitWarning === "function") {
|
|
65
|
+
proc.emitWarning(message, { type: "ZihinDeprecationWarning", code });
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
console.warn(`[${code}] ${message}`);
|
|
69
|
+
} catch {
|
|
70
|
+
}
|
|
71
|
+
}
|
|
58
72
|
|
|
59
73
|
// src/security.ts
|
|
60
74
|
var PRIVATE_IP_PATTERNS = [
|
|
@@ -876,6 +890,29 @@ async function* parseSse(response, opts = {}) {
|
|
|
876
890
|
}
|
|
877
891
|
|
|
878
892
|
// src/invoke.ts
|
|
893
|
+
function parseAcceptedFields(v) {
|
|
894
|
+
if (!Array.isArray(v)) return void 0;
|
|
895
|
+
return v.map((x) => safeId(typeof x === "string" ? x : str(rec(x).field) ?? str(rec(x).name))).filter((x) => !!x);
|
|
896
|
+
}
|
|
897
|
+
function parseIgnoredFields(v) {
|
|
898
|
+
if (!Array.isArray(v)) return void 0;
|
|
899
|
+
const out = [];
|
|
900
|
+
for (const x of v) {
|
|
901
|
+
const field = safeId(typeof x === "string" ? x : str(rec(x).field) ?? str(rec(x).name));
|
|
902
|
+
if (field) out.push(typeof x === "string" ? { field } : { field, reason: safeLabel(rec(x).reason) });
|
|
903
|
+
}
|
|
904
|
+
return out;
|
|
905
|
+
}
|
|
906
|
+
var MAX_SUGGESTIONS = 10;
|
|
907
|
+
var MAX_SUGGESTION_CHARS = 200;
|
|
908
|
+
function suggestionsFromBlocks(blocks) {
|
|
909
|
+
if (!Array.isArray(blocks)) return void 0;
|
|
910
|
+
const qr = [...blocks].reverse().find((b) => rec(b).type === "quick_replies");
|
|
911
|
+
const labels = rec(rec(qr).data).labels;
|
|
912
|
+
if (!Array.isArray(labels)) return void 0;
|
|
913
|
+
const out = labels.filter((l) => typeof l === "string" && l.trim().length > 0 && l.length <= MAX_SUGGESTION_CHARS).slice(0, MAX_SUGGESTIONS);
|
|
914
|
+
return out.length ? out : void 0;
|
|
915
|
+
}
|
|
879
916
|
function mapEvent(e) {
|
|
880
917
|
const d = rec(e.data);
|
|
881
918
|
switch (e.event) {
|
|
@@ -885,6 +922,8 @@ function mapEvent(e) {
|
|
|
885
922
|
requestId: safeId(d.request_id),
|
|
886
923
|
agentId: str(d.agent_id),
|
|
887
924
|
sessionId: safeId(d.session_id),
|
|
925
|
+
acceptedFields: parseAcceptedFields(d.accepted_fields),
|
|
926
|
+
ignoredFields: parseIgnoredFields(d.ignored_fields),
|
|
888
927
|
data: e.data
|
|
889
928
|
};
|
|
890
929
|
case "status":
|
|
@@ -940,6 +979,7 @@ function mapEvent(e) {
|
|
|
940
979
|
gated: d.gated === true ? true : void 0,
|
|
941
980
|
sources: Array.isArray(d.sources) ? d.sources : void 0,
|
|
942
981
|
blocks: Array.isArray(d.blocks) ? d.blocks : void 0,
|
|
982
|
+
suggestions: suggestionsFromBlocks(d.blocks),
|
|
943
983
|
data: e.data
|
|
944
984
|
};
|
|
945
985
|
case "metrics": {
|
|
@@ -962,6 +1002,12 @@ function buildRequestBody(params, normalizedTimeoutMs) {
|
|
|
962
1002
|
const body = {};
|
|
963
1003
|
if (params.message !== void 0) body.message = params.message;
|
|
964
1004
|
if (params.sessionId) body.session_id = params.sessionId;
|
|
1005
|
+
if (params.consumerKey) body.consumer_key = params.consumerKey;
|
|
1006
|
+
if (params.consumer?.name !== void 0) body.consumer_display_name = params.consumer.name;
|
|
1007
|
+
if (params.capabilities?.blocks?.length) body.capabilities = { blocks: [...params.capabilities.blocks] };
|
|
1008
|
+
if (params.context) body.context = { ...params.context };
|
|
1009
|
+
if (params.bindings) body.bindings = { ...params.bindings };
|
|
1010
|
+
if (params.sessionFrom?.length) body.session_from = [...params.sessionFrom];
|
|
965
1011
|
if (params.attachments?.length) {
|
|
966
1012
|
body.attachments = params.attachments.map((a) => ({ url: a.url, type: a.type }));
|
|
967
1013
|
}
|
|
@@ -1000,6 +1046,8 @@ function validateParams(params) {
|
|
|
1000
1046
|
throw new ZihinAgentError("config", sizeCheck.error || "Message too large.");
|
|
1001
1047
|
}
|
|
1002
1048
|
}
|
|
1049
|
+
validateStructuredInput(params);
|
|
1050
|
+
warnDeprecatedOptions(params);
|
|
1003
1051
|
if (params.options?.timeoutMs !== void 0) {
|
|
1004
1052
|
normalizeTimeoutMs(params.options.timeoutMs, "options.timeoutMs");
|
|
1005
1053
|
}
|
|
@@ -1010,12 +1058,193 @@ function validateParams(params) {
|
|
|
1010
1058
|
}
|
|
1011
1059
|
}
|
|
1012
1060
|
}
|
|
1013
|
-
|
|
1061
|
+
function warnDeprecatedOptions(params) {
|
|
1062
|
+
const o = params.options;
|
|
1063
|
+
if (!o) return;
|
|
1064
|
+
if (o.jitMode !== void 0) {
|
|
1065
|
+
warnDeprecatedOnce(
|
|
1066
|
+
"ZIHIN_DEP001",
|
|
1067
|
+
"`options.jitMode` is deprecated and has no effect (the server discontinued it). Remove it; it will be dropped from the SDK in 1.0."
|
|
1068
|
+
);
|
|
1069
|
+
}
|
|
1070
|
+
if (o.temperature !== void 0 || o.maxTokens !== void 0) {
|
|
1071
|
+
warnDeprecatedOnce(
|
|
1072
|
+
"ZIHIN_DEP002",
|
|
1073
|
+
"`options.temperature`/`options.maxTokens` have no effect: the agent's own configuration (`llm_config`) decides them \u2014 change it in the Zihin console or with `update_agent`. They will be dropped from the SDK in 1.0."
|
|
1074
|
+
);
|
|
1075
|
+
}
|
|
1076
|
+
}
|
|
1077
|
+
var MAX_FIELDS = 50;
|
|
1078
|
+
var MAX_VALUE_CHARS = 2e3;
|
|
1079
|
+
var MAX_TOTAL_CHARS = { context: 2e3, bindings: 16e3 };
|
|
1080
|
+
var MAX_SESSION_FROM_FIELDS = 5;
|
|
1081
|
+
var FIELD_NAME_RE = /^[A-Za-z_][A-Za-z0-9_-]{0,63}$/;
|
|
1082
|
+
var PROTOTYPE_KEYS = /* @__PURE__ */ new Set(["__proto__", "constructor", "prototype"]);
|
|
1083
|
+
var MAX_CONSUMER_KEY_CHARS = 200;
|
|
1084
|
+
var MAX_CONSUMER_NAME_CHARS = 255;
|
|
1085
|
+
var MAX_CLIENT_BLOCKS = 10;
|
|
1086
|
+
function snapshotStructuredInput(params) {
|
|
1087
|
+
const snap = (v) => isPlainObject(v) ? Object.fromEntries(Object.entries(v).filter(([, x]) => x !== void 0)) : v;
|
|
1088
|
+
return {
|
|
1089
|
+
...params,
|
|
1090
|
+
...normalizeConsumer(params),
|
|
1091
|
+
context: snap(params.context),
|
|
1092
|
+
bindings: snap(params.bindings),
|
|
1093
|
+
sessionFrom: Array.isArray(params.sessionFrom) ? [...params.sessionFrom] : params.sessionFrom,
|
|
1094
|
+
capabilities: isPlainObject(params.capabilities) ? {
|
|
1095
|
+
...params.capabilities,
|
|
1096
|
+
blocks: Array.isArray(params.capabilities.blocks) ? [...params.capabilities.blocks] : params.capabilities.blocks
|
|
1097
|
+
} : params.capabilities
|
|
1098
|
+
};
|
|
1099
|
+
}
|
|
1100
|
+
function normalizeConsumer(params) {
|
|
1101
|
+
const c = params.consumer;
|
|
1102
|
+
if (c === void 0) return {};
|
|
1103
|
+
if (!isPlainObject(c)) {
|
|
1104
|
+
throw new ZihinAgentError("config", "`consumer` must be an object: `{ key, name? }`.");
|
|
1105
|
+
}
|
|
1106
|
+
const key = c.key;
|
|
1107
|
+
if (params.consumerKey !== void 0 && key !== void 0 && params.consumerKey !== key) {
|
|
1108
|
+
throw new ZihinAgentError("config", "`consumerKey` and `consumer.key` differ \u2014 send only one of them.");
|
|
1109
|
+
}
|
|
1110
|
+
const resolved = key ?? params.consumerKey;
|
|
1111
|
+
return {
|
|
1112
|
+
consumerKey: resolved,
|
|
1113
|
+
consumer: { key: resolved, ...c.name !== void 0 ? { name: c.name } : {} }
|
|
1114
|
+
};
|
|
1115
|
+
}
|
|
1116
|
+
function isPlainObject(v) {
|
|
1117
|
+
if (typeof v !== "object" || v === null || Array.isArray(v)) return false;
|
|
1118
|
+
const proto = Object.getPrototypeOf(v);
|
|
1119
|
+
return proto === Object.prototype || proto === null;
|
|
1120
|
+
}
|
|
1121
|
+
function isScalar(v) {
|
|
1122
|
+
return v === null || typeof v === "string" || typeof v === "boolean" || typeof v === "number" && Number.isFinite(v);
|
|
1123
|
+
}
|
|
1124
|
+
function validateStructuredInput(params) {
|
|
1125
|
+
if (params.consumerKey !== void 0 && (typeof params.consumerKey !== "string" || !params.consumerKey.trim())) {
|
|
1126
|
+
throw new ZihinAgentError("config", "`consumerKey` must be a non-empty string.");
|
|
1127
|
+
}
|
|
1128
|
+
if (typeof params.consumerKey === "string" && params.consumerKey.length > MAX_CONSUMER_KEY_CHARS) {
|
|
1129
|
+
throw new ZihinAgentError("config", `\`consumerKey\` is longer than ${MAX_CONSUMER_KEY_CHARS} characters.`);
|
|
1130
|
+
}
|
|
1131
|
+
if (params.consumer !== void 0) {
|
|
1132
|
+
if (params.consumerKey === void 0) {
|
|
1133
|
+
throw new ZihinAgentError("config", "`consumer.key` is required (the name has nothing to attach to without it).");
|
|
1134
|
+
}
|
|
1135
|
+
const n = params.consumer.name;
|
|
1136
|
+
if (n !== void 0 && (typeof n !== "string" || !n.trim() || n.length > MAX_CONSUMER_NAME_CHARS)) {
|
|
1137
|
+
throw new ZihinAgentError("config", `\`consumer.name\` must be a non-empty string of up to ${MAX_CONSUMER_NAME_CHARS} characters.`);
|
|
1138
|
+
}
|
|
1139
|
+
}
|
|
1140
|
+
if (params.capabilities !== void 0) {
|
|
1141
|
+
const blocks = isPlainObject(params.capabilities) ? params.capabilities.blocks : null;
|
|
1142
|
+
if (blocks === null || blocks !== void 0 && (!Array.isArray(blocks) || blocks.length > MAX_CLIENT_BLOCKS || blocks.some((b) => typeof b !== "string"))) {
|
|
1143
|
+
throw new ZihinAgentError(
|
|
1144
|
+
"config",
|
|
1145
|
+
`\`capabilities\` must be \`{ blocks: [...] }\` with up to ${MAX_CLIENT_BLOCKS} block names (e.g. \`['quick_replies']\`).`
|
|
1146
|
+
);
|
|
1147
|
+
}
|
|
1148
|
+
}
|
|
1149
|
+
for (const name of ["context", "bindings"]) {
|
|
1150
|
+
const v = params[name];
|
|
1151
|
+
if (v === void 0) continue;
|
|
1152
|
+
if (!isPlainObject(v)) {
|
|
1153
|
+
throw new ZihinAgentError("config", `\`${name}\` must be a flat plain object.`);
|
|
1154
|
+
}
|
|
1155
|
+
const entries = Object.entries(v);
|
|
1156
|
+
if (entries.length > MAX_FIELDS) {
|
|
1157
|
+
throw new ZihinAgentError("config", `\`${name}\` accepts at most ${MAX_FIELDS} fields.`);
|
|
1158
|
+
}
|
|
1159
|
+
for (const [k, val] of entries) {
|
|
1160
|
+
if (!FIELD_NAME_RE.test(k) || PROTOTYPE_KEYS.has(k)) {
|
|
1161
|
+
throw new ZihinAgentError(
|
|
1162
|
+
"config",
|
|
1163
|
+
`Invalid field name in \`${name}\`: use letters, digits, \`_\` or \`-\`, starting with a letter or \`_\` (max 64 characters; \`constructor\`, \`prototype\` and \`__proto__\` are not allowed).`
|
|
1164
|
+
);
|
|
1165
|
+
}
|
|
1166
|
+
if (typeof val === "string" && val.length > MAX_VALUE_CHARS) {
|
|
1167
|
+
throw new ZihinAgentError("config", `\`${name}.${k}\` is longer than ${MAX_VALUE_CHARS} characters.`);
|
|
1168
|
+
}
|
|
1169
|
+
if (!isScalar(val)) {
|
|
1170
|
+
throw new ZihinAgentError(
|
|
1171
|
+
"config",
|
|
1172
|
+
`\`${name}.${k}\` must be a string, number, boolean or null \u2014 \`${name}\` is flat (no nested objects or arrays).`
|
|
1173
|
+
);
|
|
1174
|
+
}
|
|
1175
|
+
}
|
|
1176
|
+
if (JSON.stringify(v).length > MAX_TOTAL_CHARS[name]) {
|
|
1177
|
+
throw new ZihinAgentError("config", `\`${name}\` is longer than ${MAX_TOTAL_CHARS[name]} characters in total (as JSON).`);
|
|
1178
|
+
}
|
|
1179
|
+
}
|
|
1180
|
+
if (params.sessionFrom !== void 0) {
|
|
1181
|
+
if (!Array.isArray(params.sessionFrom) || params.sessionFrom.length > MAX_SESSION_FROM_FIELDS || params.sessionFrom.some((f) => typeof f !== "string" || !FIELD_NAME_RE.test(f) || PROTOTYPE_KEYS.has(f))) {
|
|
1182
|
+
throw new ZihinAgentError("config", `\`sessionFrom\` must be an array of up to ${MAX_SESSION_FROM_FIELDS} field names.`);
|
|
1183
|
+
}
|
|
1184
|
+
if (params.sessionFrom.length && params.sessionId) {
|
|
1185
|
+
throw new ZihinAgentError("config", "Use either `sessionId` or `sessionFrom`, not both.");
|
|
1186
|
+
}
|
|
1187
|
+
const available = { ...params.context, ...params.bindings };
|
|
1188
|
+
const has = (f) => Object.prototype.hasOwnProperty.call(available, f) && Boolean(available[f]);
|
|
1189
|
+
if (params.sessionFrom.length && !params.sessionFrom.some(has)) {
|
|
1190
|
+
throw new ZihinAgentError(
|
|
1191
|
+
"config",
|
|
1192
|
+
`\`sessionFrom\` names ${params.sessionFrom.map((f) => `"${f}"`).join(", ")}, but none of them has a value in \`context\` or \`bindings\`.`
|
|
1193
|
+
);
|
|
1194
|
+
}
|
|
1195
|
+
}
|
|
1196
|
+
}
|
|
1197
|
+
function requestedInputFields(params) {
|
|
1198
|
+
const out = [];
|
|
1199
|
+
if (params.context) out.push("context");
|
|
1200
|
+
if (params.bindings) out.push("bindings");
|
|
1201
|
+
if (params.sessionFrom?.length) out.push("session_from");
|
|
1202
|
+
return out;
|
|
1203
|
+
}
|
|
1204
|
+
function checkInputCapability(params, chunk) {
|
|
1205
|
+
const requested = requestedInputFields(params);
|
|
1206
|
+
const ignored = chunk?.ignoredFields ?? [];
|
|
1207
|
+
const sent = [...requested, ...params.consumerKey ? ["consumer_key"] : []];
|
|
1208
|
+
const blocking = ignored.filter(
|
|
1209
|
+
(f) => f.reason !== "redacted" && sent.some((name) => f.field === name || f.field.startsWith(`${name}.`))
|
|
1210
|
+
);
|
|
1211
|
+
const tail = "the stream was closed to cancel the turn, but the message may already be recorded in the session.";
|
|
1212
|
+
if (blocking.length) {
|
|
1213
|
+
const shown = blocking.slice(0, 5);
|
|
1214
|
+
const more = blocking.length > shown.length ? ` (+${blocking.length - shown.length} more)` : "";
|
|
1215
|
+
const what = shown.map((f) => f.reason ? `\`${f.field}\` (${f.reason})` : `\`${f.field}\``).join(", ") + more;
|
|
1216
|
+
const hint = blocking.some((f) => f.reason === "reserved_key") ? " Reserved keys are names the agent engine uses internally; rename them." : blocking.some((f) => f.reason === "requires_admin_api_key") ? " These fields only apply with an admin/owner API key." : "";
|
|
1217
|
+
throw new ZihinAgentError("config", `The server ignored ${what}.${hint} The answer is not used: ${tail}`, {
|
|
1218
|
+
requestId: chunk?.requestId
|
|
1219
|
+
});
|
|
1220
|
+
}
|
|
1221
|
+
if (requested.length && chunk?.acceptedFields === void 0) {
|
|
1222
|
+
throw new ZihinAgentError(
|
|
1223
|
+
"config",
|
|
1224
|
+
`This Zihin server does not support ${requested.map((f) => `\`${f}\``).join(", ")} on /stream yet (no \`accepted_fields\` in the stream metadata) \u2014 ${tail}`,
|
|
1225
|
+
{ requestId: chunk?.requestId }
|
|
1226
|
+
);
|
|
1227
|
+
}
|
|
1228
|
+
const mustAccept = chunk?.acceptedFields !== void 0 && params.consumerKey ? [...requested, "consumer_key"] : requested;
|
|
1229
|
+
const notAccepted = mustAccept.filter((f) => !chunk?.acceptedFields?.includes(f));
|
|
1230
|
+
if (notAccepted.length) {
|
|
1231
|
+
throw new ZihinAgentError(
|
|
1232
|
+
"config",
|
|
1233
|
+
`The server did not accept ${notAccepted.map((f) => `\`${f}\``).join(", ")} (missing from \`accepted_fields\`) \u2014 ${tail}`,
|
|
1234
|
+
{ requestId: chunk?.requestId }
|
|
1235
|
+
);
|
|
1236
|
+
}
|
|
1237
|
+
}
|
|
1238
|
+
var TURN_EVENTS = /* @__PURE__ */ new Set(["phase", "thinking", "tool", "action_proposed", "token", "response", "metrics", "done"]);
|
|
1239
|
+
async function* streamAgent(cfg, rawParams) {
|
|
1240
|
+
const params = snapshotStructuredInput(rawParams);
|
|
1014
1241
|
validateParams(params);
|
|
1015
1242
|
const callerTimeoutMs = params.options?.timeoutMs !== void 0 ? normalizeTimeoutMs(params.options.timeoutMs, "options.timeoutMs") : void 0;
|
|
1016
1243
|
const timeoutMs = callerTimeoutMs !== void 0 ? callerTimeoutMs + DEADLINE_GRACE_MS : resolveTimeout(cfg, "stream");
|
|
1017
1244
|
const reportedMs = callerTimeoutMs ?? timeoutMs;
|
|
1018
1245
|
const url = `${cfg.baseUrl}/api/v2/agents/${encodeURIComponent(params.agentId)}/stream`;
|
|
1246
|
+
let needsCapability = requestedInputFields(params).length > 0 || !!params.consumerKey;
|
|
1247
|
+
let gated = false;
|
|
1019
1248
|
const { response, deadline, endpoint } = await openStream({
|
|
1020
1249
|
url,
|
|
1021
1250
|
headers: buildHeaders(cfg.apiKey),
|
|
@@ -1043,7 +1272,14 @@ async function* streamAgent(cfg, params) {
|
|
|
1043
1272
|
maxEventBytes: cfg.maxStreamEventBytes,
|
|
1044
1273
|
maxTotalBytes: cfg.maxStreamTotalBytes
|
|
1045
1274
|
})) {
|
|
1046
|
-
|
|
1275
|
+
const chunk = mapEvent(e);
|
|
1276
|
+
if (needsCapability) {
|
|
1277
|
+
if (chunk.type === "metadata") checkInputCapability(params, chunk);
|
|
1278
|
+
else if (chunk.type === "status" && chunk.phase === "GATED") gated = true;
|
|
1279
|
+
else if (TURN_EVENTS.has(chunk.type) && !gated) checkInputCapability(params, void 0);
|
|
1280
|
+
if (chunk.type === "metadata" || TURN_EVENTS.has(chunk.type) && !gated) needsCapability = false;
|
|
1281
|
+
}
|
|
1282
|
+
yield chunk;
|
|
1047
1283
|
}
|
|
1048
1284
|
} finally {
|
|
1049
1285
|
deadline.dispose();
|
|
@@ -1051,6 +1287,7 @@ async function* streamAgent(cfg, params) {
|
|
|
1051
1287
|
}
|
|
1052
1288
|
function classifyStreamErrorKind(code, timedOut) {
|
|
1053
1289
|
if (timedOut === true) return "timeout";
|
|
1290
|
+
if (code === "VALIDATION_ERROR") return "config";
|
|
1054
1291
|
if (timedOut === false) return "server";
|
|
1055
1292
|
return /TIMEOUT/i.test(code) ? "timeout" : "server";
|
|
1056
1293
|
}
|
|
@@ -1143,6 +1380,8 @@ async function invokeAgent(cfg, params) {
|
|
|
1143
1380
|
let mintedSessionId;
|
|
1144
1381
|
let doneChunk;
|
|
1145
1382
|
let blocked;
|
|
1383
|
+
let acceptedFields;
|
|
1384
|
+
let ignoredFields;
|
|
1146
1385
|
let tokenBuffer = "";
|
|
1147
1386
|
for await (const chunk of streamAgent(cfg, params)) {
|
|
1148
1387
|
if (keepRaw) raw.push(chunk);
|
|
@@ -1152,8 +1391,11 @@ async function invokeAgent(cfg, params) {
|
|
|
1152
1391
|
if (chunk.type === "response") responseChunk = chunk;
|
|
1153
1392
|
else if (chunk.type === "metrics") metricsChunk = chunk;
|
|
1154
1393
|
else if (chunk.type === "token") tokenBuffer += chunk.content;
|
|
1155
|
-
else if (chunk.type === "metadata"
|
|
1156
|
-
|
|
1394
|
+
else if (chunk.type === "metadata") {
|
|
1395
|
+
if (chunk.sessionId) metadataSessionId = chunk.sessionId;
|
|
1396
|
+
acceptedFields = chunk.acceptedFields;
|
|
1397
|
+
ignoredFields = chunk.ignoredFields;
|
|
1398
|
+
} else if (chunk.type === "session" && chunk.sessionId) mintedSessionId = chunk.sessionId;
|
|
1157
1399
|
else if (chunk.type === "done") doneChunk = chunk;
|
|
1158
1400
|
else if (chunk.type === "status" && chunk.blocked) blocked = chunk.blocked;
|
|
1159
1401
|
}
|
|
@@ -1176,6 +1418,8 @@ async function invokeAgent(cfg, params) {
|
|
|
1176
1418
|
iterations: metricsChunk?.iterations,
|
|
1177
1419
|
executionTimeMs: metricsChunk?.executionTimeMs,
|
|
1178
1420
|
executionId,
|
|
1421
|
+
acceptedFields,
|
|
1422
|
+
ignoredFields,
|
|
1179
1423
|
raw
|
|
1180
1424
|
};
|
|
1181
1425
|
}
|
|
@@ -1189,6 +1433,7 @@ async function invokeAgent(cfg, params) {
|
|
|
1189
1433
|
usage: metricsChunk?.usage,
|
|
1190
1434
|
sources: responseChunk.sources,
|
|
1191
1435
|
blocks: responseChunk.blocks,
|
|
1436
|
+
suggestions: responseChunk.suggestions,
|
|
1192
1437
|
gated: responseChunk.gated,
|
|
1193
1438
|
iterations: metricsChunk?.iterations,
|
|
1194
1439
|
executionTimeMs: metricsChunk?.executionTimeMs,
|
|
@@ -1196,6 +1441,8 @@ async function invokeAgent(cfg, params) {
|
|
|
1196
1441
|
outcome: doneChunk?.outcome,
|
|
1197
1442
|
truncated: doneChunk?.truncated,
|
|
1198
1443
|
finishReason: doneChunk?.finishReason,
|
|
1444
|
+
acceptedFields,
|
|
1445
|
+
ignoredFields,
|
|
1199
1446
|
raw
|
|
1200
1447
|
};
|
|
1201
1448
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zihin/agent-client",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.1",
|
|
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",
|