@zihin/agent-client 0.7.0 → 0.9.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.
@@ -0,0 +1,2220 @@
1
+ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
2
+ get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
3
+ }) : x)(function(x) {
4
+ if (typeof require !== "undefined") return require.apply(this, arguments);
5
+ throw Error('Dynamic require of "' + x + '" is not supported');
6
+ });
7
+
8
+ // src/internal.ts
9
+ var _utf8 = new TextEncoder();
10
+ function utf8Len(s) {
11
+ return _utf8.encode(s).length;
12
+ }
13
+ function rec(x) {
14
+ return x && typeof x === "object" ? x : {};
15
+ }
16
+ function safeId(x) {
17
+ return typeof x === "string" && /^[\w.:-]{1,128}$/.test(x) ? x : void 0;
18
+ }
19
+ function safeLabel(x) {
20
+ return typeof x === "string" && /^[A-Za-z0-9_]{1,64}$/.test(x) ? x : void 0;
21
+ }
22
+ function str(x) {
23
+ return typeof x === "string" ? x : void 0;
24
+ }
25
+ function num(x) {
26
+ return typeof x === "number" ? x : void 0;
27
+ }
28
+ function parseUsage(usageRaw) {
29
+ const u = rec(usageRaw);
30
+ if (Object.keys(u).length === 0) return void 0;
31
+ return {
32
+ inputTokens: num(u.input_tokens) ?? num(u.prompt_tokens),
33
+ outputTokens: num(u.output_tokens) ?? num(u.completion_tokens),
34
+ totalTokens: num(u.total_tokens),
35
+ costUsd: num(u.cost_usd)
36
+ };
37
+ }
38
+ var RUNNING_STATUSES = /* @__PURE__ */ new Set([
39
+ "running",
40
+ "pending",
41
+ "queued",
42
+ "processing",
43
+ "in_progress",
44
+ "waiting",
45
+ "retrying",
46
+ "scheduled",
47
+ "awaiting_approval",
48
+ // HITL: turno pausado esperando decisão humana não terminou
49
+ // Estados VIVOS reais do backend (zihin-agent-builder `TaskService.LIVE_STATUSES` e
50
+ // `agent_executions.status`): sem eles, uma task/execução pausada ou aguardando input
51
+ // era lida como TERMINAL e o polling parava com o trabalho ainda em andamento.
52
+ "paused",
53
+ "awaiting_input"
54
+ ]);
55
+ function isTerminalStatus(status) {
56
+ return !RUNNING_STATUSES.has(status.toLowerCase().replace(/-/g, "_"));
57
+ }
58
+ var _warnedDeprecations = /* @__PURE__ */ new Set();
59
+ function warnDeprecatedOnce(code, message, warn) {
60
+ if (_warnedDeprecations.has(code)) return;
61
+ _warnedDeprecations.add(code);
62
+ try {
63
+ warn?.(code, message);
64
+ } catch {
65
+ }
66
+ }
67
+
68
+ // src/security.ts
69
+ var PRIVATE_IP_PATTERNS = [
70
+ /^127\./,
71
+ // 127.0.0.0/8 (localhost)
72
+ /^10\./,
73
+ // 10.0.0.0/8 (private)
74
+ /^172\.(1[6-9]|2[0-9]|3[0-1])\./,
75
+ // 172.16.0.0/12 (private)
76
+ /^192\.168\./,
77
+ // 192.168.0.0/16 (private)
78
+ /^169\.254\./,
79
+ // 169.254.0.0/16 (link-local)
80
+ /^0\./
81
+ // 0.0.0.0/8
82
+ ];
83
+ function parseIpv6(host) {
84
+ if (!host.includes(":")) return null;
85
+ let text = host;
86
+ const words = [];
87
+ const v4 = /(\d{1,3}(?:\.\d{1,3}){3})$/.exec(text);
88
+ let tail = [];
89
+ if (v4) {
90
+ const octets = v4[1].split(".").map(Number);
91
+ if (octets.some((o) => !Number.isInteger(o) || o < 0 || o > 255)) return null;
92
+ tail = [octets[0] << 8 | octets[1], octets[2] << 8 | octets[3]];
93
+ text = text.slice(0, v4.index).replace(/:$/, "") || "::";
94
+ }
95
+ const halves = text.split("::");
96
+ if (halves.length > 2) return null;
97
+ const toWords = (part) => {
98
+ if (!part) return [];
99
+ const out = [];
100
+ for (const group of part.split(":")) {
101
+ if (!/^[0-9a-f]{1,4}$/i.test(group)) return null;
102
+ out.push(parseInt(group, 16));
103
+ }
104
+ return out;
105
+ };
106
+ const head = toWords(halves[0]);
107
+ const rest = halves.length === 2 ? toWords(halves[1]) : [];
108
+ if (!head || !rest) return null;
109
+ if (halves.length === 2) {
110
+ const fill = 8 - (head.length + rest.length + tail.length);
111
+ if (fill < 0) return null;
112
+ words.push(...head, ...Array(fill).fill(0), ...rest, ...tail);
113
+ } else {
114
+ words.push(...head, ...tail);
115
+ }
116
+ return words.length === 8 ? words : null;
117
+ }
118
+ function isPrivateIpv6(words) {
119
+ const [w0, w1, w2, w3, w4, w5] = words;
120
+ const allZero = words.every((w) => w === 0);
121
+ if (allZero) return true;
122
+ if (words.slice(0, 7).every((w) => w === 0) && words[7] === 1) return true;
123
+ if (w0 === 0 && w1 === 0 && w2 === 0 && w3 === 0 && w4 === 0 && (w5 === 0 || w5 === 65535)) {
124
+ const v4 = `${words[6] >> 8}.${words[6] & 255}.${words[7] >> 8}.${words[7] & 255}`;
125
+ return PRIVATE_IP_PATTERNS.some((p) => p.test(v4));
126
+ }
127
+ if ((w0 >> 8 & 254) === 252) return true;
128
+ if ((w0 & 65472) === 65152) return true;
129
+ if ((w0 & 65472) === 65216) return true;
130
+ return false;
131
+ }
132
+ function bareHostname(hostname) {
133
+ const bare = hostname.startsWith("[") && hostname.endsWith("]") ? hostname.slice(1, -1) : hostname;
134
+ return bare.length > 1 && bare.endsWith(".") ? bare.replace(/\.+$/, "") : bare;
135
+ }
136
+ var BLOCKED_HOSTNAMES = [
137
+ "localhost",
138
+ "localtest.me",
139
+ "metadata.google.internal",
140
+ // GCP metadata
141
+ "169.254.169.254",
142
+ // AWS/Azure/GCP metadata
143
+ "metadata.azure.com"
144
+ ];
145
+ var ALLOWED_ZIHIN_DOMAINS = [
146
+ "llm.zihin.ai",
147
+ "api.zihin.ai",
148
+ "staging.llm.zihin.ai",
149
+ "dev.llm.zihin.ai"
150
+ ];
151
+ function validateBaseUrl(url, options = {}) {
152
+ if (!url || url.trim() === "") {
153
+ return { valid: true, sanitizedUrl: "https://llm.zihin.ai" };
154
+ }
155
+ try {
156
+ const parsed = new URL(url);
157
+ if (parsed.hostname.length > 1 && parsed.hostname.endsWith(".")) {
158
+ parsed.hostname = parsed.hostname.replace(/\.+$/, "");
159
+ }
160
+ const hostname = bareHostname(parsed.hostname);
161
+ const isLocalhost = hostname === "localhost" || hostname === "127.0.0.1" || hostname === "::1";
162
+ if (isLocalhost && options.allowLocalhost) {
163
+ const sanitizedUrl2 = parsed.origin + parsed.pathname.replace(/\/+$/, "");
164
+ return { valid: true, sanitizedUrl: sanitizedUrl2 };
165
+ }
166
+ if (!options.allowHttp && parsed.protocol !== "https:") {
167
+ return {
168
+ valid: false,
169
+ error: "Only HTTPS URLs are allowed for security reasons"
170
+ };
171
+ }
172
+ const ipv6 = parseIpv6(hostname);
173
+ if (ipv6) {
174
+ if (isPrivateIpv6(ipv6)) {
175
+ return { valid: false, error: "Private IP addresses are not allowed" };
176
+ }
177
+ } else {
178
+ for (const pattern of PRIVATE_IP_PATTERNS) {
179
+ if (pattern.test(hostname)) {
180
+ return { valid: false, error: "Private IP addresses are not allowed" };
181
+ }
182
+ }
183
+ }
184
+ for (const blocked of BLOCKED_HOSTNAMES) {
185
+ if (blocked.startsWith("*")) {
186
+ const suffix = blocked.slice(1);
187
+ if (hostname.endsWith(suffix)) {
188
+ return { valid: false, error: "This hostname is not allowed" };
189
+ }
190
+ } else if (hostname === blocked || hostname.endsWith("." + blocked)) {
191
+ return { valid: false, error: "This hostname is not allowed" };
192
+ }
193
+ }
194
+ if (!options.allowCustomDomains) {
195
+ const isAllowed = ALLOWED_ZIHIN_DOMAINS.some(
196
+ (domain) => hostname === domain || hostname.endsWith("." + domain)
197
+ );
198
+ if (!isAllowed) {
199
+ return {
200
+ valid: false,
201
+ error: `Only Zihin domains are allowed. Use 'allowCustomBaseUrl' for custom endpoints.`
202
+ };
203
+ }
204
+ }
205
+ const sanitizedUrl = parsed.origin + parsed.pathname.replace(/\/+$/, "");
206
+ return { valid: true, sanitizedUrl };
207
+ } catch {
208
+ return { valid: false, error: "Invalid URL format" };
209
+ }
210
+ }
211
+ var DEFAULT_LIMITS = {
212
+ maxPromptBytes: 100 * 1024,
213
+ // 100KB
214
+ maxMessageCount: 100,
215
+ maxSingleMessageBytes: 50 * 1024,
216
+ // 50KB
217
+ maxTotalPayloadBytes: 500 * 1024
218
+ // 500KB
219
+ };
220
+ function validatePromptSize(prompt, limits = {}) {
221
+ const effectiveLimits = { ...DEFAULT_LIMITS, ...limits };
222
+ const bytes = utf8Len(prompt);
223
+ if (bytes > effectiveLimits.maxPromptBytes) {
224
+ return {
225
+ valid: false,
226
+ error: `Prompt size (${Math.round(bytes / 1024)}KB) exceeds maximum allowed (${Math.round(
227
+ effectiveLimits.maxPromptBytes / 1024
228
+ )}KB)`,
229
+ details: { promptBytes: bytes }
230
+ };
231
+ }
232
+ return { valid: true, details: { promptBytes: bytes } };
233
+ }
234
+ var SENSITIVE_PATTERNS = [
235
+ /zhn_(?:live|test|dev)_[a-zA-Z0-9]+/g,
236
+ // API keys
237
+ /Bearer\s+[a-zA-Z0-9._-]+/gi,
238
+ // Bearer tokens
239
+ /at\s+[\w./<>]+\s*\(.*:\d+:\d+\)/g,
240
+ // Stack trace lines
241
+ /^\s+at\s+.+$/gm,
242
+ // Stack traces
243
+ /Error:\s*.*\n\s+at/g
244
+ // Error stack beginnings
245
+ ];
246
+ var ERROR_CODE_MAP = {
247
+ RATE_LIMIT_EXCEEDED: "Rate limit exceeded. Please wait before retrying.",
248
+ UNAUTHORIZED: "Authentication failed. Please check your API credentials.",
249
+ FORBIDDEN: "This API key's role does not allow this action. The required role depends on the endpoint \u2014 check the key's role in Zihin \u2192 Settings \u2192 API Keys.",
250
+ INVALID_MODEL: "The specified model is not available.",
251
+ INTERNAL_ERROR: "An internal error occurred. Please try again later.",
252
+ TIMEOUT: "Request timed out. Try increasing the timeout.",
253
+ INVALID_REQUEST: "The request was malformed. Please check your input.",
254
+ INSUFFICIENT_CREDITS: "Insufficient credits. Please check your account balance.",
255
+ CONTEXT_LENGTH_EXCEEDED: "The input exceeds the model context length limit.",
256
+ AGENT_NOT_FOUND: "Agent not found or not active for this tenant.",
257
+ // Nome novo do `AGENT_NOT_FOUND` (zihin-agent-builder#561, fatia B). `AGENT_ERROR` virou
258
+ // `INTERNAL_ERROR`, que já está acima.
259
+ NOT_FOUND: "Resource not found \u2014 check the ID (agent, trigger or execution) and that it belongs to this workspace.",
260
+ // Bloqueios comerciais. As mensagens do BE vêm em português e estes shells são em inglês;
261
+ // além disso cada caso tem uma ação DIFERENTE, e o texto precisa dizer qual.
262
+ TRIAL_EXPIRED: "Your Zihin trial has ended. Upgrade the plan to keep invoking agents \u2014 the API key is fine.",
263
+ QUOTA_EXCEEDED: "Your Zihin token quota is exhausted. Upgrade the plan or wait for the next cycle \u2014 retrying will not help, and the API key is fine.",
264
+ NO_ACTIVE_SUBSCRIPTION: "This tenant has no active Zihin subscription. Activate a plan to invoke agents \u2014 the API key is fine."
265
+ };
266
+ function sanitizeApiError(error) {
267
+ let message = "An unexpected error occurred";
268
+ let code;
269
+ let retryable = false;
270
+ let retryAfter;
271
+ if (typeof error === "object" && error !== null) {
272
+ const err = error;
273
+ const nested = err.error && typeof err.error === "object" ? err.error : void 0;
274
+ const codeRaw = nested?.code ?? (typeof err.error === "string" ? err.error : err.code);
275
+ code = typeof codeRaw === "string" ? codeRaw : void 0;
276
+ const rawMessage = nested?.message ?? err.message;
277
+ const reason = typeof err.reason === "string" ? err.reason : void 0;
278
+ const effectiveCode = code === "QUOTA_EXCEEDED" && reason === "no_active_subscription" ? "NO_ACTIVE_SUBSCRIPTION" : code;
279
+ if (effectiveCode && ERROR_CODE_MAP[effectiveCode]) {
280
+ message = ERROR_CODE_MAP[effectiveCode];
281
+ } else if (typeof rawMessage === "string" && rawMessage) {
282
+ message = rawMessage;
283
+ }
284
+ if (code === "RATE_LIMIT_EXCEEDED" || code === "TIMEOUT") {
285
+ retryable = true;
286
+ retryAfter = err.retryAfter || 60;
287
+ }
288
+ if (message.length > 2e3) message = message.slice(0, 2e3);
289
+ for (const pattern of SENSITIVE_PATTERNS) {
290
+ message = message.replace(pattern, "[REDACTED]");
291
+ }
292
+ } else if (typeof error === "string") {
293
+ message = error;
294
+ if (message.length > 2e3) message = message.slice(0, 2e3);
295
+ for (const pattern of SENSITIVE_PATTERNS) {
296
+ message = message.replace(pattern, "[REDACTED]");
297
+ }
298
+ }
299
+ if (message.length > 500) {
300
+ message = message.substring(0, 500) + "...";
301
+ }
302
+ return { message, code, retryable, retryAfter };
303
+ }
304
+ var API_KEY_PATTERN = /^zhn_(live|test|dev)_[a-zA-Z0-9]{20,}$/;
305
+ function validateApiKeyFormat(apiKey) {
306
+ if (!apiKey || apiKey.trim() === "") {
307
+ return { valid: false, error: "API key is required" };
308
+ }
309
+ const trimmed = apiKey.trim();
310
+ if (!API_KEY_PATTERN.test(trimmed)) {
311
+ return {
312
+ valid: false,
313
+ error: "Invalid API key format. Expected: zhn_live_xxx, zhn_test_xxx or zhn_dev_xxx"
314
+ };
315
+ }
316
+ const environment = trimmed.startsWith("zhn_live_") ? "live" : trimmed.startsWith("zhn_test_") ? "test" : "dev";
317
+ return { valid: true, environment };
318
+ }
319
+
320
+ // src/errors.ts
321
+ var ZihinAgentError = class _ZihinAgentError extends Error {
322
+ constructor(kind, message, opts = {}) {
323
+ super(buildMessage(message, opts));
324
+ this.name = "ZihinAgentError";
325
+ this.kind = kind;
326
+ this.status = opts.status;
327
+ this.code = opts.code;
328
+ this.requestId = opts.requestId;
329
+ this.executionId = opts.executionId;
330
+ this.sessionId = opts.sessionId;
331
+ this.timeoutClock = opts.timeoutClock;
332
+ this.attempts = opts.attempts;
333
+ this.endpoint = opts.endpoint;
334
+ this.retryable = opts.retryable ?? (kind === "rate_limit" || kind === "server" || kind === "network");
335
+ Object.setPrototypeOf(this, _ZihinAgentError.prototype);
336
+ }
337
+ };
338
+ function buildMessage(base, opts) {
339
+ let s = base;
340
+ if (opts.endpoint) s += ` [${opts.endpoint}]`;
341
+ if (opts.attempts && opts.attempts > 1) s += ` (after ${opts.attempts} attempts)`;
342
+ if (opts.requestId) s += ` (request_id: ${opts.requestId})`;
343
+ if (opts.executionId) s += ` (execution_id: ${opts.executionId})`;
344
+ return s;
345
+ }
346
+ var COMMERCIAL_BLOCK_CODES = /* @__PURE__ */ new Set(["TRIAL_EXPIRED", "QUOTA_EXCEEDED", "NO_ACTIVE_SUBSCRIPTION"]);
347
+ function errorCodeOf(body) {
348
+ const b = rec(body);
349
+ return str(b.error) ?? str(rec(b.error).code) ?? str(b.code);
350
+ }
351
+ function isCommercialBlock(body) {
352
+ const code = errorCodeOf(body);
353
+ return code !== void 0 && COMMERCIAL_BLOCK_CODES.has(code);
354
+ }
355
+ function commercialKind(body) {
356
+ const code = errorCodeOf(body);
357
+ if (code === "TRIAL_EXPIRED") return "trial_expired";
358
+ if (code === "NO_ACTIVE_SUBSCRIPTION") return "no_subscription";
359
+ if (code === "QUOTA_EXCEEDED") {
360
+ return str(rec(body).reason) === "no_active_subscription" ? "no_subscription" : "quota_exceeded";
361
+ }
362
+ return void 0;
363
+ }
364
+ function extractRequestId(body) {
365
+ const b = rec(body);
366
+ return safeId(b.request_id) ?? safeId(b.requestId) ?? safeId(rec(b.error).request_id) ?? safeId(rec(b.meta).request_id);
367
+ }
368
+ function errorFromResponse(status, body, meta = {}) {
369
+ const sanitized = sanitizeApiError(body);
370
+ const message = sanitized.message;
371
+ const code = sanitized.code;
372
+ const base = {
373
+ status,
374
+ code,
375
+ requestId: extractRequestId(body),
376
+ executionId: safeId(rec(body).execution_id),
377
+ ...meta
378
+ };
379
+ const commercial = commercialKind(body);
380
+ if (commercial) {
381
+ return new ZihinAgentError(commercial, message || "Request blocked by your plan.", {
382
+ ...base,
383
+ retryable: false
384
+ });
385
+ }
386
+ if (code === "PLAN_LIMIT_REACHED")
387
+ return new ZihinAgentError("blocked", message || "This feature is not available on your Zihin plan.", { ...base, retryable: false });
388
+ if (status === 401) return new ZihinAgentError("auth", message || "Authentication failed.", base);
389
+ if (status === 403 && str(rec(rec(rec(body).error).details).reason) === "AGENT_ACCESS_DENIED")
390
+ return new ZihinAgentError(
391
+ "not_found",
392
+ "Agent not found, inactive, or not accessible with this API key \u2014 check the agent ID and that the key belongs to the same Zihin workspace.",
393
+ { ...base, retryable: false }
394
+ );
395
+ if (status === 403)
396
+ return new ZihinAgentError(
397
+ "auth",
398
+ // Não enumerar papéis aqui: o papel exigido varia por rota (triggers aceitam editor,
399
+ // mas delegar/cancelar uma task exige admin/owner). A frase antiga mandava o usuário
400
+ // para o papel errado em metade da API.
401
+ message || "This API key's role does not allow this action. The required role depends on the endpoint \u2014 check the key's role in Zihin \u2192 Settings \u2192 API Keys.",
402
+ base
403
+ );
404
+ if (status === 404) return new ZihinAgentError("not_found", message || "Resource not found.", base);
405
+ if (status === 408)
406
+ return new ZihinAgentError("timeout", message || "The server timed out handling the request.", {
407
+ ...base,
408
+ retryable: true
409
+ });
410
+ if (status === 429)
411
+ return new ZihinAgentError("rate_limit", message || "Rate limit exceeded.", { ...base, retryable: true });
412
+ if (status >= 500)
413
+ return new ZihinAgentError("server", message || `Server error (${status}).`, { ...base, retryable: true });
414
+ return new ZihinAgentError("unknown", message || `Request failed (${status}).`, base);
415
+ }
416
+ var NON_RETRYABLE_NET = /^(ENOTFOUND|EAI_AGAIN|CERT_|ERR_TLS|UNABLE_TO_VERIFY|DEPTH_ZERO)/;
417
+ function errorFromThrow(err, timeoutMs, meta = {}) {
418
+ if (err instanceof ZihinAgentError) return err;
419
+ const name = err?.name;
420
+ if (name === "TimeoutError") {
421
+ return new ZihinAgentError("timeout", `Request timed out after ${timeoutMs ?? "?"}ms.`, {
422
+ ...meta,
423
+ retryable: false
424
+ });
425
+ }
426
+ if (name === "AbortError") {
427
+ return new ZihinAgentError("aborted", "Request was aborted by the caller.", { ...meta, retryable: false });
428
+ }
429
+ const cause = err?.cause;
430
+ const causeCode = cause?.code;
431
+ const sanitized = sanitizeApiError(err);
432
+ const detail = causeCode || sanitized.message || "Network error";
433
+ const retryable = causeCode ? !NON_RETRYABLE_NET.test(causeCode) : true;
434
+ return new ZihinAgentError("network", `Network error: ${detail}`, { ...meta, retryable });
435
+ }
436
+
437
+ // src/http.ts
438
+ var DEFAULT_BASE_URL = "https://llm.zihin.ai";
439
+ var OPERATION_TIMEOUT_MS = Object.freeze({
440
+ stream: 2e5,
441
+ dispatch: 26e4,
442
+ short: 35e3
443
+ });
444
+ var DEADLINE_GRACE_MS = 3e4;
445
+ var RETRY_OVERHEAD_MS = 6e4;
446
+ var MIN_TIMEOUT_MS = 1e3;
447
+ var MAX_TIMEOUT_MS = 9e5;
448
+ function normalizeTimeoutMs(value, label = "timeoutMs") {
449
+ const n = typeof value === "number" ? value : Number(value);
450
+ if (!Number.isFinite(n) || n <= 0) {
451
+ throw new ZihinAgentError(
452
+ "config",
453
+ `${label} must be a positive number of milliseconds (got ${JSON.stringify(value)}).`
454
+ );
455
+ }
456
+ return Math.min(Math.max(n, MIN_TIMEOUT_MS), MAX_TIMEOUT_MS);
457
+ }
458
+ var DEFAULT_MAX_RETRIES = 2;
459
+ var DEFAULT_MAX_EVENT_BYTES = 1 * 1024 * 1024;
460
+ var DEFAULT_MAX_TOTAL_BYTES = 64 * 1024 * 1024;
461
+ function globalFetch() {
462
+ return typeof fetch === "function" ? fetch : void 0;
463
+ }
464
+ function resolveFetch(injected) {
465
+ const f = injected ?? globalFetch();
466
+ if (typeof f !== "function") {
467
+ throw new ZihinAgentError("config", "Global fetch is unavailable in this runtime.");
468
+ }
469
+ return f;
470
+ }
471
+ function resolveConfig(config) {
472
+ const keyCheck = validateApiKeyFormat(config.apiKey ?? "");
473
+ if (!keyCheck.valid) {
474
+ throw new ZihinAgentError("config", keyCheck.error || "Invalid API key.");
475
+ }
476
+ const urlCheck = validateBaseUrl(config.baseUrl ?? "", {
477
+ allowCustomDomains: config.allowCustomBaseUrl,
478
+ allowHttp: config.allowHttp,
479
+ allowLocalhost: config.allowHttp
480
+ });
481
+ if (!urlCheck.valid) {
482
+ throw new ZihinAgentError("config", urlCheck.error || "Invalid base URL.");
483
+ }
484
+ if (typeof config.runtime?.sleep !== "function") {
485
+ throw new ZihinAgentError(
486
+ "config",
487
+ 'A runtime is required: import from "@zihin/agent-client" (default runtime) or pass `runtime` to the "@zihin/agent-client/core" client.'
488
+ );
489
+ }
490
+ if (typeof (config.fetch ?? globalFetch()) !== "function") {
491
+ throw new ZihinAgentError(
492
+ "config",
493
+ "Global fetch is unavailable in this runtime. Use Node >=20.16 or pass a `fetch` implementation in the client config."
494
+ );
495
+ }
496
+ return {
497
+ apiKey: config.apiKey.trim(),
498
+ baseUrl: urlCheck.sanitizedUrl || DEFAULT_BASE_URL,
499
+ timeoutMs: config.timeoutMs === void 0 || config.timeoutMs === null ? void 0 : normalizeTimeoutMs(config.timeoutMs, "timeoutMs"),
500
+ maxRetries: config.maxRetries ?? DEFAULT_MAX_RETRIES,
501
+ maxStreamEventBytes: config.maxStreamEventBytes ?? DEFAULT_MAX_EVENT_BYTES,
502
+ maxStreamTotalBytes: config.maxStreamTotalBytes ?? DEFAULT_MAX_TOTAL_BYTES,
503
+ fetchImpl: config.fetch,
504
+ runtime: config.runtime,
505
+ onRetry: config.onRetry,
506
+ onError: config.onError
507
+ };
508
+ }
509
+ function resolveTimeout(cfg, operation) {
510
+ return cfg.timeoutMs ?? OPERATION_TIMEOUT_MS[operation];
511
+ }
512
+ function endpointLabel(method, url) {
513
+ try {
514
+ const u = new URL(url);
515
+ return `${method} ${u.host}${u.pathname}`;
516
+ } catch {
517
+ return method;
518
+ }
519
+ }
520
+ function retryAfterMs(headers, capMs = 3e4) {
521
+ const raw = headers.get("retry-after");
522
+ if (!raw) return void 0;
523
+ const secs = Number(raw);
524
+ if (Number.isFinite(secs)) return Math.min(Math.max(0, secs * 1e3), capMs);
525
+ const when = Date.parse(raw);
526
+ if (Number.isFinite(when)) return Math.min(Math.max(0, when - Date.now()), capMs);
527
+ return void 0;
528
+ }
529
+ function buildHeaders(apiKey, extra = {}) {
530
+ return {
531
+ "Content-Type": "application/json",
532
+ Accept: "application/json",
533
+ "X-Api-Key": apiKey,
534
+ ...extra
535
+ };
536
+ }
537
+ async function delay(runtime, ms, signal, meta) {
538
+ const aborted = () => new ZihinAgentError("aborted", "Request was aborted by the caller.", meta);
539
+ if (signal?.aborted) throw aborted();
540
+ try {
541
+ await runtime.sleep(ms, signal);
542
+ } catch {
543
+ throw aborted();
544
+ }
545
+ }
546
+ function backoffMs(attempt) {
547
+ const base = Math.min(1e3 * 2 ** attempt, 1e4);
548
+ return base / 2 + Math.random() * (base / 2);
549
+ }
550
+ function createDeadline(runtime, timeoutMs, callerSignal) {
551
+ const controller = new AbortController();
552
+ const clock = new AbortController();
553
+ let timedOut = false;
554
+ let abortedByCaller = false;
555
+ runtime.sleep(timeoutMs, clock.signal).then(
556
+ () => {
557
+ timedOut = true;
558
+ controller.abort();
559
+ },
560
+ () => {
561
+ }
562
+ );
563
+ const onAbort = () => {
564
+ abortedByCaller = true;
565
+ controller.abort();
566
+ };
567
+ if (callerSignal) {
568
+ if (callerSignal.aborted) onAbort();
569
+ else callerSignal.addEventListener("abort", onAbort, { once: true });
570
+ }
571
+ return {
572
+ signal: controller.signal,
573
+ dispose: () => {
574
+ clock.abort();
575
+ if (callerSignal) callerSignal.removeEventListener("abort", onAbort);
576
+ },
577
+ timedOut: () => timedOut,
578
+ abortedByCaller: () => abortedByCaller
579
+ };
580
+ }
581
+ function shouldRetryStatus(status, policy) {
582
+ if (policy === "idempotent") return status >= 500 || status === 429 || status === 408;
583
+ return status === 429 || status === 503;
584
+ }
585
+ function budgetTracker(args) {
586
+ const startedAt = Date.now();
587
+ const reported = args.reportedTimeoutMs ?? args.timeoutMs;
588
+ const overheadCap = Math.min(args.timeoutMs, RETRY_OVERHEAD_MS);
589
+ return {
590
+ reported,
591
+ /** Quanto ainda pode ser gasto com tentativas falhas + backoff. */
592
+ overheadLeft: () => overheadCap - (Date.now() - startedAt),
593
+ expired: (attempts) => new ZihinAgentError("timeout", `Request timed out after ${reported}ms.`, {
594
+ endpoint: args.endpoint,
595
+ attempts
596
+ })
597
+ };
598
+ }
599
+ async function requestBuffered(args) {
600
+ const endpoint = endpointLabel(args.method, args.url);
601
+ const doFetch = resolveFetch(args.fetchImpl);
602
+ const budget = budgetTracker({ ...args, endpoint });
603
+ let lastError = null;
604
+ for (let attempt = 0; attempt <= args.maxRetries; attempt++) {
605
+ const d = createDeadline(args.runtime, args.timeoutMs, args.callerSignal);
606
+ try {
607
+ const res = await doFetch(args.url, {
608
+ method: args.method,
609
+ headers: args.headers,
610
+ body: args.body,
611
+ signal: d.signal,
612
+ redirect: "error",
613
+ // 3xx para outro host vazaria a X-Api-Key
614
+ cache: "no-store"
615
+ // evita cache stale (ex.: polling em Next.js/edge)
616
+ });
617
+ let wait = shouldRetryStatus(res.status, args.policy) && attempt < args.maxRetries ? retryAfterMs(res.headers) ?? backoffMs(attempt) : null;
618
+ let peeked;
619
+ if (wait !== null && res.status === 429) {
620
+ peeked = await res.text();
621
+ if (isCommercialBlock(parseBody(peeked))) wait = null;
622
+ }
623
+ if (wait !== null && budget.overheadLeft() > wait) {
624
+ if (peeked === void 0) {
625
+ try {
626
+ await res.body?.cancel();
627
+ } catch {
628
+ }
629
+ }
630
+ d.dispose();
631
+ args.onRetry?.({ endpoint, attempt: attempt + 1, delayMs: wait, reason: "status", status: res.status });
632
+ await delay(args.runtime, wait, args.callerSignal, { endpoint, attempts: attempt + 1 });
633
+ continue;
634
+ }
635
+ const text = peeked ?? await res.text();
636
+ d.dispose();
637
+ return { status: res.status, ok: res.ok, text, endpoint, attempts: attempt + 1 };
638
+ } catch (err) {
639
+ d.dispose();
640
+ if (err instanceof ZihinAgentError) throw err;
641
+ const meta = { endpoint, attempts: attempt + 1 };
642
+ if (d.timedOut()) throw budget.expired(attempt + 1);
643
+ if (d.abortedByCaller())
644
+ throw new ZihinAgentError("aborted", "Request was aborted by the caller.", meta);
645
+ lastError = errorFromThrow(err, budget.reported, meta);
646
+ const canRetry = args.policy === "idempotent" && lastError.retryable && attempt < args.maxRetries;
647
+ if (!canRetry) throw lastError;
648
+ const wait = backoffMs(attempt);
649
+ if (budget.overheadLeft() <= wait) throw lastError;
650
+ args.onRetry?.({ endpoint, attempt: attempt + 1, delayMs: wait, reason: "network" });
651
+ await delay(args.runtime, wait, args.callerSignal, { endpoint, attempts: attempt + 1 });
652
+ }
653
+ }
654
+ throw lastError ?? new ZihinAgentError("unknown", "Max retries exceeded.", { endpoint });
655
+ }
656
+ async function openStream(args) {
657
+ const endpoint = endpointLabel("POST", args.url);
658
+ const doFetch = resolveFetch(args.fetchImpl);
659
+ const budget = budgetTracker({ ...args, endpoint });
660
+ const policy = args.idempotent ? "idempotent" : "side-effecting";
661
+ for (let attempt = 0; attempt <= args.maxRetries; attempt++) {
662
+ const d = createDeadline(args.runtime, args.timeoutMs, args.callerSignal);
663
+ try {
664
+ const response = await doFetch(args.url, {
665
+ method: "POST",
666
+ headers: { ...args.headers, Accept: "text/event-stream" },
667
+ body: args.body,
668
+ signal: d.signal,
669
+ redirect: "error",
670
+ cache: "no-store"
671
+ });
672
+ let wait = shouldRetryStatus(response.status, policy) && attempt < args.maxRetries ? retryAfterMs(response.headers) ?? backoffMs(attempt) : null;
673
+ if (response.status === 429) {
674
+ const text = await response.text().catch(() => "");
675
+ const body = parseBody(text);
676
+ if (wait === null || isCommercialBlock(body) || budget.overheadLeft() <= wait) {
677
+ d.dispose();
678
+ throw errorFromResponse(response.status, body, { endpoint, attempts: attempt + 1 });
679
+ }
680
+ d.dispose();
681
+ args.onRetry?.({ endpoint, attempt: attempt + 1, delayMs: wait, reason: "status", status: 429 });
682
+ await delay(args.runtime, wait, args.callerSignal, { endpoint, attempts: attempt + 1 });
683
+ continue;
684
+ }
685
+ if (wait !== null && budget.overheadLeft() > wait) {
686
+ try {
687
+ await response.body?.cancel();
688
+ } catch {
689
+ }
690
+ d.dispose();
691
+ args.onRetry?.({
692
+ endpoint,
693
+ attempt: attempt + 1,
694
+ delayMs: wait,
695
+ reason: "status",
696
+ status: response.status
697
+ });
698
+ await delay(args.runtime, wait, args.callerSignal, { endpoint, attempts: attempt + 1 });
699
+ continue;
700
+ }
701
+ return { response, deadline: d, endpoint };
702
+ } catch (err) {
703
+ d.dispose();
704
+ if (err instanceof ZihinAgentError) throw err;
705
+ const meta = { endpoint, attempts: attempt + 1 };
706
+ if (d.timedOut()) throw budget.expired(attempt + 1);
707
+ if (d.abortedByCaller())
708
+ throw new ZihinAgentError("aborted", "Request was aborted by the caller.", meta);
709
+ const netErr = errorFromThrow(err, budget.reported, meta);
710
+ const wait = backoffMs(attempt);
711
+ if (policy === "idempotent" && netErr.retryable && attempt < args.maxRetries && budget.overheadLeft() > wait) {
712
+ args.onRetry?.({ endpoint, attempt: attempt + 1, delayMs: wait, reason: "network" });
713
+ await delay(args.runtime, wait, args.callerSignal, { endpoint, attempts: attempt + 1 });
714
+ continue;
715
+ }
716
+ throw netErr;
717
+ }
718
+ }
719
+ throw new ZihinAgentError("unknown", "Max retries exceeded.", { endpoint });
720
+ }
721
+ function toJsonBody(value, label = "body") {
722
+ try {
723
+ return JSON.stringify(value);
724
+ } catch (err) {
725
+ throw new ZihinAgentError(
726
+ "config",
727
+ `${label} is not JSON-serializable: ${err?.message ?? "unknown reason"}.`
728
+ );
729
+ }
730
+ }
731
+ async function requestJson(cfg, args) {
732
+ const res = await requestBuffered({
733
+ method: args.method,
734
+ url: `${cfg.baseUrl}${args.path}`,
735
+ headers: buildHeaders(cfg.apiKey),
736
+ body: args.body !== void 0 ? toJsonBody(args.body) : void 0,
737
+ timeoutMs: resolveTimeout(cfg, args.operation ?? "short"),
738
+ maxRetries: cfg.maxRetries,
739
+ policy: args.policy,
740
+ callerSignal: args.signal,
741
+ fetchImpl: cfg.fetchImpl,
742
+ runtime: cfg.runtime,
743
+ onRetry: cfg.onRetry
744
+ });
745
+ if (!res.ok) {
746
+ throw errorFromResponse(res.status, parseBody(res.text), {
747
+ endpoint: res.endpoint,
748
+ attempts: res.attempts
749
+ });
750
+ }
751
+ try {
752
+ return JSON.parse(res.text);
753
+ } catch {
754
+ throw new ZihinAgentError("protocol", "Expected JSON response.", { endpoint: res.endpoint });
755
+ }
756
+ }
757
+ function parseBody(text) {
758
+ if (!text) return {};
759
+ try {
760
+ return JSON.parse(text);
761
+ } catch {
762
+ return { message: text };
763
+ }
764
+ }
765
+
766
+ // src/agents.ts
767
+ async function listAgents(cfg, opts = {}) {
768
+ const res = await requestBuffered({
769
+ method: "GET",
770
+ url: `${cfg.baseUrl}/api/v2/agents`,
771
+ headers: buildHeaders(cfg.apiKey),
772
+ timeoutMs: resolveTimeout(cfg, "short"),
773
+ maxRetries: cfg.maxRetries,
774
+ policy: "idempotent",
775
+ callerSignal: opts.signal,
776
+ fetchImpl: cfg.fetchImpl,
777
+ runtime: cfg.runtime,
778
+ onRetry: cfg.onRetry
779
+ });
780
+ if (!res.ok) {
781
+ throw errorFromResponse(res.status, parseBody(res.text), { endpoint: res.endpoint, attempts: res.attempts });
782
+ }
783
+ let json;
784
+ try {
785
+ json = JSON.parse(res.text);
786
+ } catch {
787
+ throw new ZihinAgentError("protocol", "Expected JSON from GET /api/v2/agents.");
788
+ }
789
+ const data = rec(json).data;
790
+ const list = Array.isArray(data) ? data : [];
791
+ return list.map((item) => {
792
+ const a = rec(item);
793
+ return {
794
+ id: str(a.id) ?? "",
795
+ name: str(a.name),
796
+ commercialName: str(a.commercial_name),
797
+ bio: str(a.bio),
798
+ type: str(a.type),
799
+ status: str(a.status),
800
+ tags: Array.isArray(a.tags) ? a.tags.filter((t) => typeof t === "string") : void 0,
801
+ toolsCount: typeof a.tools_count === "number" ? a.tools_count : void 0
802
+ };
803
+ });
804
+ }
805
+
806
+ // src/sse.ts
807
+ var DEFAULT_MAX_EVENT_BYTES2 = 1 * 1024 * 1024;
808
+ var DEFAULT_MAX_TOTAL_BYTES2 = 64 * 1024 * 1024;
809
+ async function* parseSse(response, opts = {}) {
810
+ if (!response.body) {
811
+ throw new ZihinAgentError("protocol", "SSE response has no body.");
812
+ }
813
+ const maxEventBytes = opts.maxEventBytes ?? DEFAULT_MAX_EVENT_BYTES2;
814
+ const maxTotalBytes = opts.maxTotalBytes ?? DEFAULT_MAX_TOTAL_BYTES2;
815
+ const reader = response.body.getReader();
816
+ const decoder = new TextDecoder("utf-8");
817
+ let buffer = "";
818
+ let eventName = "";
819
+ let dataLines = [];
820
+ let dataBytes = 0;
821
+ let totalBytes = 0;
822
+ const flush = () => {
823
+ const had = dataLines.length > 0 || eventName !== "";
824
+ const rawData = dataLines.join("\n");
825
+ const evt = eventName || "message";
826
+ eventName = "";
827
+ dataLines = [];
828
+ dataBytes = 0;
829
+ if (!had || rawData === "") return null;
830
+ let data = rawData;
831
+ try {
832
+ data = JSON.parse(rawData);
833
+ } catch {
834
+ }
835
+ return { event: evt, data, rawData };
836
+ };
837
+ try {
838
+ while (true) {
839
+ if (opts.signal?.aborted) {
840
+ throw opts.onAbortError ? opts.onAbortError() : new ZihinAgentError("aborted", "Stream aborted.");
841
+ }
842
+ let chunk;
843
+ try {
844
+ chunk = await reader.read();
845
+ } catch (err) {
846
+ throw opts.onAbortError ? opts.onAbortError() : errorFromThrow(err);
847
+ }
848
+ if (chunk.done) break;
849
+ totalBytes += chunk.value.byteLength;
850
+ if (totalBytes > maxTotalBytes) {
851
+ throw new ZihinAgentError("protocol", `SSE stream exceeded ${maxTotalBytes} bytes.`);
852
+ }
853
+ buffer += decoder.decode(chunk.value, { stream: true });
854
+ let nl;
855
+ while ((nl = buffer.indexOf("\n")) !== -1) {
856
+ let line = buffer.slice(0, nl);
857
+ buffer = buffer.slice(nl + 1);
858
+ if (line.endsWith("\r")) line = line.slice(0, -1);
859
+ if (line === "") {
860
+ const e = flush();
861
+ if (e) yield e;
862
+ continue;
863
+ }
864
+ if (line.startsWith(":")) continue;
865
+ const colon = line.indexOf(":");
866
+ const field = colon === -1 ? line : line.slice(0, colon);
867
+ let val = colon === -1 ? "" : line.slice(colon + 1);
868
+ if (val.startsWith(" ")) val = val.slice(1);
869
+ if (field === "event") {
870
+ eventName = val;
871
+ } else if (field === "data") {
872
+ dataBytes += utf8Len(val) + 1;
873
+ if (dataBytes > maxEventBytes) {
874
+ throw new ZihinAgentError("protocol", `SSE event exceeded ${maxEventBytes} bytes.`);
875
+ }
876
+ dataLines.push(val);
877
+ }
878
+ }
879
+ if (utf8Len(buffer) > maxEventBytes) {
880
+ throw new ZihinAgentError("protocol", `SSE line exceeded ${maxEventBytes} bytes.`);
881
+ }
882
+ }
883
+ buffer += decoder.decode();
884
+ if (buffer.length > 0) {
885
+ let line = buffer.endsWith("\r") ? buffer.slice(0, -1) : buffer;
886
+ if (line && !line.startsWith(":")) {
887
+ const colon = line.indexOf(":");
888
+ const field = colon === -1 ? line : line.slice(0, colon);
889
+ let val = colon === -1 ? "" : line.slice(colon + 1);
890
+ if (val.startsWith(" ")) val = val.slice(1);
891
+ if (field === "data") dataLines.push(val);
892
+ else if (field === "event") eventName = val;
893
+ }
894
+ }
895
+ const tail = flush();
896
+ if (tail) yield tail;
897
+ } finally {
898
+ try {
899
+ await reader.cancel();
900
+ } catch {
901
+ }
902
+ opts.onEnd?.();
903
+ }
904
+ }
905
+
906
+ // src/invoke.ts
907
+ function parseAcceptedFields(v) {
908
+ if (!Array.isArray(v)) return void 0;
909
+ return v.map((x) => safeId(typeof x === "string" ? x : str(rec(x).field) ?? str(rec(x).name))).filter((x) => !!x);
910
+ }
911
+ function parseIgnoredFields(v) {
912
+ if (!Array.isArray(v)) return void 0;
913
+ const out = [];
914
+ for (const x of v) {
915
+ const field = safeId(typeof x === "string" ? x : str(rec(x).field) ?? str(rec(x).name));
916
+ if (field) out.push(typeof x === "string" ? { field } : { field, reason: safeLabel(rec(x).reason) });
917
+ }
918
+ return out;
919
+ }
920
+ var MAX_SUGGESTIONS = 10;
921
+ var MAX_SUGGESTION_CHARS = 200;
922
+ function suggestionsFromBlocks(blocks) {
923
+ if (!Array.isArray(blocks)) return void 0;
924
+ const qr = [...blocks].reverse().find((b) => rec(b).type === "quick_replies");
925
+ const labels = rec(rec(qr).data).labels;
926
+ if (!Array.isArray(labels)) return void 0;
927
+ const out = labels.filter((l) => typeof l === "string" && l.trim().length > 0 && l.length <= MAX_SUGGESTION_CHARS).slice(0, MAX_SUGGESTIONS);
928
+ return out.length ? out : void 0;
929
+ }
930
+ function turnIds(d) {
931
+ return {
932
+ executionId: safeId(d.execution_id),
933
+ clientMessageId: safeId(d.client_message_id),
934
+ idempotentReplay: d.idempotent_replay === true ? true : void 0,
935
+ userMessageId: safeId(d.user_message_id),
936
+ assistantMessageId: safeId(d.assistant_message_id)
937
+ };
938
+ }
939
+ function mapEvent(e) {
940
+ const d = rec(e.data);
941
+ switch (e.event) {
942
+ case "metadata":
943
+ return {
944
+ type: "metadata",
945
+ requestId: safeId(d.request_id),
946
+ agentId: str(d.agent_id),
947
+ sessionId: safeId(d.session_id),
948
+ ...turnIds(d),
949
+ acceptedFields: parseAcceptedFields(d.accepted_fields),
950
+ ignoredFields: parseIgnoredFields(d.ignored_fields),
951
+ data: e.data
952
+ };
953
+ case "status":
954
+ return { type: "status", phase: str(d.phase), blocked: safeLabel(d.blocked), data: e.data };
955
+ case "attachments":
956
+ return { type: "attachments", data: e.data };
957
+ case "phase":
958
+ return { type: "phase", phase: str(d.phase), data: e.data };
959
+ case "thinking":
960
+ return { type: "thinking", thought: str(d.thought), data: e.data };
961
+ case "tool":
962
+ return {
963
+ type: "tool",
964
+ tool: str(d.tool),
965
+ callId: str(d.call_id),
966
+ success: typeof d.success === "boolean" ? d.success : void 0,
967
+ data: e.data
968
+ };
969
+ case "action_proposed":
970
+ return { type: "action_proposed", data: e.data };
971
+ case "action_forwarded":
972
+ return { type: "action_forwarded", actionId: safeId(d.action_id), data: e.data };
973
+ case "token":
974
+ return { type: "token", content: str(d.content) ?? "", data: e.data };
975
+ case "warning":
976
+ return { type: "warning", message: str(d.message), data: e.data };
977
+ case "session":
978
+ return { type: "session", sessionId: safeId(d.session_id), data: e.data };
979
+ case "done":
980
+ return {
981
+ type: "done",
982
+ outcome: safeLabel(d.outcome),
983
+ timedOut: typeof d.timed_out === "boolean" ? d.timed_out : void 0,
984
+ timeoutClock: safeLabel(d.timeout_clock),
985
+ truncated: typeof d.truncated === "boolean" ? d.truncated : void 0,
986
+ finishReason: str(d.finish_reason),
987
+ ...turnIds(d),
988
+ data: e.data
989
+ };
990
+ case "error": {
991
+ const err = rec(d.error);
992
+ return {
993
+ type: "error",
994
+ code: safeLabel(err.code),
995
+ message: str(err.message),
996
+ requestId: safeId(err.request_id),
997
+ timedOut: typeof err.timed_out === "boolean" ? err.timed_out : void 0,
998
+ data: e.data
999
+ };
1000
+ }
1001
+ case "response":
1002
+ return {
1003
+ type: "response",
1004
+ content: str(d.content) ?? "",
1005
+ sessionId: safeId(d.session_id),
1006
+ gated: d.gated === true ? true : void 0,
1007
+ sources: Array.isArray(d.sources) ? d.sources : void 0,
1008
+ blocks: Array.isArray(d.blocks) ? d.blocks : void 0,
1009
+ suggestions: suggestionsFromBlocks(d.blocks),
1010
+ ...turnIds(d),
1011
+ data: e.data
1012
+ };
1013
+ case "metrics": {
1014
+ const modelConfig = rec(d.model_config);
1015
+ return {
1016
+ type: "metrics",
1017
+ usage: parseUsage(d.usage),
1018
+ model: str(modelConfig.model) ?? str(d.model),
1019
+ iterations: num(d.iterations),
1020
+ executionTimeMs: num(d.execution_time_ms),
1021
+ executionId: safeId(d.execution_id),
1022
+ data: e.data
1023
+ };
1024
+ }
1025
+ default:
1026
+ return { type: "unknown", event: e.event, data: e.data };
1027
+ }
1028
+ }
1029
+ function buildRequestBody(params, normalizedTimeoutMs) {
1030
+ const body = {};
1031
+ if (params.message !== void 0) body.message = params.message;
1032
+ if (params.sessionId) body.session_id = params.sessionId;
1033
+ if (params.clientMessageId) body.client_message_id = params.clientMessageId;
1034
+ if (params.consumerKey) body.consumer_key = params.consumerKey;
1035
+ if (params.consumer?.name !== void 0) body.consumer_display_name = params.consumer.name;
1036
+ if (params.capabilities?.blocks?.length) body.capabilities = { blocks: [...params.capabilities.blocks] };
1037
+ if (params.context) body.context = { ...params.context };
1038
+ if (params.bindings) body.bindings = { ...params.bindings };
1039
+ if (params.sessionFrom?.length) body.session_from = [...params.sessionFrom];
1040
+ if (params.attachments?.length) {
1041
+ body.attachments = params.attachments.map((a) => ({ url: a.url, type: a.type }));
1042
+ }
1043
+ if (params.options) {
1044
+ const o = {};
1045
+ if (params.options.temperature !== void 0) o.temperature = params.options.temperature;
1046
+ if (params.options.maxTokens !== void 0) o.max_tokens = params.options.maxTokens;
1047
+ if (normalizedTimeoutMs !== void 0) o.timeout_ms = normalizedTimeoutMs;
1048
+ if (params.options.jitMode !== void 0) o.jit_mode = params.options.jitMode;
1049
+ if (Object.keys(o).length) body.options = o;
1050
+ }
1051
+ if (params.metadata) {
1052
+ const m = { ...params.metadata };
1053
+ if (params.metadata.traceId !== void 0) {
1054
+ m.trace_id = params.metadata.traceId;
1055
+ delete m.traceId;
1056
+ }
1057
+ if (params.metadata.correlationId !== void 0) {
1058
+ m.correlation_id = params.metadata.correlationId;
1059
+ delete m.correlationId;
1060
+ }
1061
+ body.metadata = m;
1062
+ }
1063
+ return body;
1064
+ }
1065
+ function validateParams(params, warn) {
1066
+ if (!params.agentId) {
1067
+ throw new ZihinAgentError("config", "agentId is required.");
1068
+ }
1069
+ if (!params.message && !(params.attachments && params.attachments.length)) {
1070
+ throw new ZihinAgentError("config", "Either `message` or `attachments` is required.");
1071
+ }
1072
+ if (params.message) {
1073
+ const sizeCheck = validatePromptSize(params.message);
1074
+ if (!sizeCheck.valid) {
1075
+ throw new ZihinAgentError("config", sizeCheck.error || "Message too large.");
1076
+ }
1077
+ }
1078
+ validateStructuredInput(params);
1079
+ warnDeprecatedOptions(params, warn);
1080
+ if (params.options?.timeoutMs !== void 0) {
1081
+ normalizeTimeoutMs(params.options.timeoutMs, "options.timeoutMs");
1082
+ }
1083
+ for (const att of params.attachments ?? []) {
1084
+ const urlCheck = validateBaseUrl(att.url, { allowCustomDomains: true });
1085
+ if (!urlCheck.valid) {
1086
+ throw new ZihinAgentError("config", `Invalid attachment URL: ${urlCheck.error}`);
1087
+ }
1088
+ }
1089
+ }
1090
+ function warnDeprecatedOptions(params, warn) {
1091
+ const o = params.options;
1092
+ if (!o) return;
1093
+ if (o.jitMode !== void 0) {
1094
+ warnDeprecatedOnce(
1095
+ "ZIHIN_DEP001",
1096
+ "`options.jitMode` is deprecated and has no effect (the server discontinued it). Remove it; it will be dropped from the SDK in 1.0.",
1097
+ warn
1098
+ );
1099
+ }
1100
+ if (o.temperature !== void 0 || o.maxTokens !== void 0) {
1101
+ warnDeprecatedOnce(
1102
+ "ZIHIN_DEP002",
1103
+ "`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.",
1104
+ warn
1105
+ );
1106
+ }
1107
+ }
1108
+ var MAX_FIELDS = 50;
1109
+ var MAX_VALUE_CHARS = 2e3;
1110
+ var MAX_TOTAL_CHARS = { context: 2e3, bindings: 16e3 };
1111
+ var MAX_SESSION_FROM_FIELDS = 5;
1112
+ var FIELD_NAME_RE = /^[A-Za-z_][A-Za-z0-9_-]{0,63}$/;
1113
+ var PROTOTYPE_KEYS = /* @__PURE__ */ new Set(["__proto__", "constructor", "prototype"]);
1114
+ var MAX_CONSUMER_KEY_CHARS = 200;
1115
+ var MAX_CONSUMER_NAME_CHARS = 255;
1116
+ var MAX_CLIENT_BLOCKS = 10;
1117
+ var UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
1118
+ function snapshotStructuredInput(params) {
1119
+ const snap = (v) => isPlainObject(v) ? Object.fromEntries(Object.entries(v).filter(([, x]) => x !== void 0)) : v;
1120
+ return {
1121
+ ...params,
1122
+ ...normalizeConsumer(params),
1123
+ context: snap(params.context),
1124
+ bindings: snap(params.bindings),
1125
+ sessionFrom: Array.isArray(params.sessionFrom) ? [...params.sessionFrom] : params.sessionFrom,
1126
+ capabilities: isPlainObject(params.capabilities) ? {
1127
+ ...params.capabilities,
1128
+ blocks: Array.isArray(params.capabilities.blocks) ? [...params.capabilities.blocks] : params.capabilities.blocks
1129
+ } : params.capabilities
1130
+ };
1131
+ }
1132
+ function normalizeConsumer(params) {
1133
+ const c = params.consumer;
1134
+ if (c === void 0) return {};
1135
+ if (!isPlainObject(c)) {
1136
+ throw new ZihinAgentError("config", "`consumer` must be an object: `{ key, name? }`.");
1137
+ }
1138
+ const key = c.key;
1139
+ if (params.consumerKey !== void 0 && key !== void 0 && params.consumerKey !== key) {
1140
+ throw new ZihinAgentError("config", "`consumerKey` and `consumer.key` differ \u2014 send only one of them.");
1141
+ }
1142
+ const resolved = key ?? params.consumerKey;
1143
+ return {
1144
+ consumerKey: resolved,
1145
+ consumer: { key: resolved, ...c.name !== void 0 ? { name: c.name } : {} }
1146
+ };
1147
+ }
1148
+ function isPlainObject(v) {
1149
+ if (typeof v !== "object" || v === null || Array.isArray(v)) return false;
1150
+ const proto = Object.getPrototypeOf(v);
1151
+ return proto === Object.prototype || proto === null;
1152
+ }
1153
+ function isScalar(v) {
1154
+ return v === null || typeof v === "string" || typeof v === "boolean" || typeof v === "number" && Number.isFinite(v);
1155
+ }
1156
+ function validateStructuredInput(params) {
1157
+ if (params.clientMessageId !== void 0 && (typeof params.clientMessageId !== "string" || !UUID_RE.test(params.clientMessageId))) {
1158
+ throw new ZihinAgentError("config", "`clientMessageId` must be a UUID (e.g. from `crypto.randomUUID()`).");
1159
+ }
1160
+ if (params.consumerKey !== void 0 && (typeof params.consumerKey !== "string" || !params.consumerKey.trim())) {
1161
+ throw new ZihinAgentError("config", "`consumerKey` must be a non-empty string.");
1162
+ }
1163
+ if (typeof params.consumerKey === "string" && params.consumerKey.length > MAX_CONSUMER_KEY_CHARS) {
1164
+ throw new ZihinAgentError("config", `\`consumerKey\` is longer than ${MAX_CONSUMER_KEY_CHARS} characters.`);
1165
+ }
1166
+ if (params.consumer !== void 0) {
1167
+ if (params.consumerKey === void 0) {
1168
+ throw new ZihinAgentError("config", "`consumer.key` is required (the name has nothing to attach to without it).");
1169
+ }
1170
+ const n = params.consumer.name;
1171
+ if (n !== void 0 && (typeof n !== "string" || !n.trim() || n.length > MAX_CONSUMER_NAME_CHARS)) {
1172
+ throw new ZihinAgentError("config", `\`consumer.name\` must be a non-empty string of up to ${MAX_CONSUMER_NAME_CHARS} characters.`);
1173
+ }
1174
+ }
1175
+ if (params.capabilities !== void 0) {
1176
+ const blocks = isPlainObject(params.capabilities) ? params.capabilities.blocks : null;
1177
+ if (blocks === null || blocks !== void 0 && (!Array.isArray(blocks) || blocks.length > MAX_CLIENT_BLOCKS || blocks.some((b) => typeof b !== "string"))) {
1178
+ throw new ZihinAgentError(
1179
+ "config",
1180
+ `\`capabilities\` must be \`{ blocks: [...] }\` with up to ${MAX_CLIENT_BLOCKS} block names (e.g. \`['quick_replies']\`).`
1181
+ );
1182
+ }
1183
+ }
1184
+ for (const name of ["context", "bindings"]) {
1185
+ const v = params[name];
1186
+ if (v === void 0) continue;
1187
+ if (!isPlainObject(v)) {
1188
+ throw new ZihinAgentError("config", `\`${name}\` must be a flat plain object.`);
1189
+ }
1190
+ const entries = Object.entries(v);
1191
+ if (entries.length > MAX_FIELDS) {
1192
+ throw new ZihinAgentError("config", `\`${name}\` accepts at most ${MAX_FIELDS} fields.`);
1193
+ }
1194
+ for (const [k, val] of entries) {
1195
+ if (!FIELD_NAME_RE.test(k) || PROTOTYPE_KEYS.has(k)) {
1196
+ throw new ZihinAgentError(
1197
+ "config",
1198
+ `Invalid field name in \`${name}\`: use letters, digits, \`_\` or \`-\`, starting with a letter or \`_\` (max 64 characters; \`constructor\`, \`prototype\` and \`__proto__\` are not allowed).`
1199
+ );
1200
+ }
1201
+ if (typeof val === "string" && val.length > MAX_VALUE_CHARS) {
1202
+ throw new ZihinAgentError("config", `\`${name}.${k}\` is longer than ${MAX_VALUE_CHARS} characters.`);
1203
+ }
1204
+ if (!isScalar(val)) {
1205
+ throw new ZihinAgentError(
1206
+ "config",
1207
+ `\`${name}.${k}\` must be a string, number, boolean or null \u2014 \`${name}\` is flat (no nested objects or arrays).`
1208
+ );
1209
+ }
1210
+ }
1211
+ if (JSON.stringify(v).length > MAX_TOTAL_CHARS[name]) {
1212
+ throw new ZihinAgentError("config", `\`${name}\` is longer than ${MAX_TOTAL_CHARS[name]} characters in total (as JSON).`);
1213
+ }
1214
+ }
1215
+ if (params.sessionFrom !== void 0) {
1216
+ 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))) {
1217
+ throw new ZihinAgentError("config", `\`sessionFrom\` must be an array of up to ${MAX_SESSION_FROM_FIELDS} field names.`);
1218
+ }
1219
+ if (params.sessionFrom.length && params.sessionId) {
1220
+ throw new ZihinAgentError("config", "Use either `sessionId` or `sessionFrom`, not both.");
1221
+ }
1222
+ const available = { ...params.context, ...params.bindings };
1223
+ const has = (f) => Object.prototype.hasOwnProperty.call(available, f) && Boolean(available[f]);
1224
+ if (params.sessionFrom.length && !params.sessionFrom.some(has)) {
1225
+ throw new ZihinAgentError(
1226
+ "config",
1227
+ `\`sessionFrom\` names ${params.sessionFrom.map((f) => `"${f}"`).join(", ")}, but none of them has a value in \`context\` or \`bindings\`.`
1228
+ );
1229
+ }
1230
+ }
1231
+ }
1232
+ function requestedInputFields(params) {
1233
+ const out = [];
1234
+ if (params.context) out.push("context");
1235
+ if (params.bindings) out.push("bindings");
1236
+ if (params.sessionFrom?.length) out.push("session_from");
1237
+ if (params.clientMessageId) out.push("client_message_id");
1238
+ return out;
1239
+ }
1240
+ function checkInputCapability(params, chunk) {
1241
+ const requested = requestedInputFields(params);
1242
+ const ignored = chunk?.ignoredFields ?? [];
1243
+ const sent = [...requested, ...params.consumerKey ? ["consumer_key"] : []];
1244
+ const blocking = ignored.filter(
1245
+ (f) => f.reason !== "redacted" && sent.some((name) => f.field === name || f.field.startsWith(`${name}.`))
1246
+ );
1247
+ const tail = "the stream was closed to cancel the turn, but the message may already be recorded in the session.";
1248
+ if (blocking.length) {
1249
+ const shown = blocking.slice(0, 5);
1250
+ const more = blocking.length > shown.length ? ` (+${blocking.length - shown.length} more)` : "";
1251
+ const what = shown.map((f) => f.reason ? `\`${f.field}\` (${f.reason})` : `\`${f.field}\``).join(", ") + more;
1252
+ 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." : "";
1253
+ throw new ZihinAgentError("config", `The server ignored ${what}.${hint} The answer is not used: ${tail}`, {
1254
+ requestId: chunk?.requestId
1255
+ });
1256
+ }
1257
+ if (requested.length && chunk?.acceptedFields === void 0) {
1258
+ throw new ZihinAgentError(
1259
+ "config",
1260
+ `This Zihin server does not support ${requested.map((f) => `\`${f}\``).join(", ")} on /stream yet (no \`accepted_fields\` in the stream metadata) \u2014 ${tail}`,
1261
+ { requestId: chunk?.requestId }
1262
+ );
1263
+ }
1264
+ const mustAccept = chunk?.acceptedFields !== void 0 && params.consumerKey ? [...requested, "consumer_key"] : requested;
1265
+ const notAccepted = mustAccept.filter((f) => !chunk?.acceptedFields?.includes(f));
1266
+ if (notAccepted.length) {
1267
+ throw new ZihinAgentError(
1268
+ "config",
1269
+ `The server did not accept ${notAccepted.map((f) => `\`${f}\``).join(", ")} (missing from \`accepted_fields\`) \u2014 ${tail}`,
1270
+ { requestId: chunk?.requestId }
1271
+ );
1272
+ }
1273
+ }
1274
+ var TURN_EVENTS = /* @__PURE__ */ new Set(["phase", "thinking", "tool", "action_proposed", "action_forwarded", "token", "response", "metrics", "done"]);
1275
+ async function* streamAgent(cfg, rawParams) {
1276
+ const params = snapshotStructuredInput(rawParams);
1277
+ validateParams(params, cfg.runtime.warn);
1278
+ const callerTimeoutMs = params.options?.timeoutMs !== void 0 ? normalizeTimeoutMs(params.options.timeoutMs, "options.timeoutMs") : void 0;
1279
+ const timeoutMs = callerTimeoutMs !== void 0 ? callerTimeoutMs + DEADLINE_GRACE_MS : resolveTimeout(cfg, "stream");
1280
+ const reportedMs = callerTimeoutMs ?? timeoutMs;
1281
+ const url = `${cfg.baseUrl}/api/v2/agents/${encodeURIComponent(params.agentId)}/stream`;
1282
+ let needsCapability = requestedInputFields(params).length > 0 || !!params.consumerKey;
1283
+ let gated = false;
1284
+ const { response, deadline, endpoint } = await openStream({
1285
+ url,
1286
+ headers: buildHeaders(cfg.apiKey),
1287
+ body: toJsonBody(buildRequestBody(params, callerTimeoutMs), "request body"),
1288
+ timeoutMs,
1289
+ reportedTimeoutMs: reportedMs,
1290
+ maxRetries: cfg.maxRetries,
1291
+ callerSignal: params.signal,
1292
+ fetchImpl: cfg.fetchImpl,
1293
+ runtime: cfg.runtime,
1294
+ onRetry: cfg.onRetry,
1295
+ // Retry do POST só ANTES dos headers chegarem. O que o torna seguro é a ordem do BE: o SSE faz
1296
+ // `flushHeaders()` antes de reservar o id e antes de qualquer trabalho do turno, então falhar
1297
+ // sem headers quase sempre significa que o handler nem começou. A chave cobre o resto: se o 1º
1298
+ // POST chegou a rodar, o reenvio dá SESSION_BUSY (em voo) ou replay (COMPLETED/LENGTH). A chave
1299
+ // NÃO protege turno cortado no meio: o BE cancela e libera o id, e o reenvio roda de novo com as
1300
+ // tools — por isso o erro mid-stream sai `retryable: false`. Liga antes de ver `accepted_fields`
1301
+ // (chega no `metadata`): contra BE antigo o retry pré-headers acontece sem a chave valer —
1302
+ // aceitável pela mesma ordem (headers antes do trabalho), mas documentado no README.
1303
+ // Resíduo não verificado: proxy que bufferiza headers e devolve 502/504 depois do BE já ter
1304
+ // começado (X-Accel-Buffering: no torna isso improvável).
1305
+ idempotent: !!params.clientMessageId
1306
+ });
1307
+ try {
1308
+ if (!response.ok) {
1309
+ let text = "";
1310
+ try {
1311
+ text = await response.text();
1312
+ } catch {
1313
+ }
1314
+ throw errorFromResponse(response.status, parseBody(text), { endpoint });
1315
+ }
1316
+ const onAbortError = () => deadline.timedOut() ? new ZihinAgentError("timeout", `Stream timed out after ${reportedMs}ms.`, { endpoint }) : deadline.abortedByCaller() ? new ZihinAgentError("aborted", "Stream aborted by the caller.", { endpoint }) : new ZihinAgentError("network", "Stream connection interrupted.", { endpoint, retryable: false });
1317
+ for await (const e of parseSse(response, {
1318
+ signal: params.signal,
1319
+ onAbortError,
1320
+ maxEventBytes: cfg.maxStreamEventBytes,
1321
+ maxTotalBytes: cfg.maxStreamTotalBytes
1322
+ })) {
1323
+ const chunk = mapEvent(e);
1324
+ if (needsCapability) {
1325
+ if (chunk.type === "metadata") checkInputCapability(params, chunk);
1326
+ else if (chunk.type === "status" && chunk.phase === "GATED") gated = true;
1327
+ else if (TURN_EVENTS.has(chunk.type) && !gated) checkInputCapability(params, void 0);
1328
+ if (chunk.type === "metadata" || TURN_EVENTS.has(chunk.type) && !gated) needsCapability = false;
1329
+ }
1330
+ yield chunk;
1331
+ }
1332
+ } finally {
1333
+ deadline.dispose();
1334
+ }
1335
+ }
1336
+ function classifyStreamErrorKind(code, timedOut) {
1337
+ if (timedOut === true) return "timeout";
1338
+ if (code === "VALIDATION_ERROR") return "config";
1339
+ const known = own(STREAM_ERROR_CODES, code);
1340
+ if (known) return known.kind;
1341
+ if (timedOut === false) return "server";
1342
+ return /TIMEOUT/i.test(code) ? "timeout" : "server";
1343
+ }
1344
+ var STREAM_ERROR_CODES = {
1345
+ SESSION_BUSY: {
1346
+ kind: "blocked",
1347
+ message: "Another turn is still running in this session (or the same clientMessageId is still in flight); retry after it finishes.",
1348
+ retryable: true
1349
+ },
1350
+ IDEMPOTENCY_CONFLICT: {
1351
+ kind: "config",
1352
+ message: "This `clientMessageId` was already used with a different message \u2014 use a new UUID per message."
1353
+ },
1354
+ // `keepServerMessage`: a frase do BE diz o que foi e o que NÃO foi feito (tools que rodaram) —
1355
+ // o integrador precisa dela para decidir se reenvia.
1356
+ CONTEXT_LIMIT: {
1357
+ kind: "blocked",
1358
+ message: "The conversation gathered more context than the model can process; send a smaller request or start a new session.",
1359
+ keepServerMessage: true
1360
+ },
1361
+ CONFLICT: {
1362
+ kind: "blocked",
1363
+ message: "The declared consumer identity does not match the one already bound to this session."
1364
+ },
1365
+ NOT_FOUND: {
1366
+ kind: "not_found",
1367
+ message: "Agent not found, inactive, or not accessible with this API key \u2014 check the agent ID and that the key belongs to the same Zihin workspace."
1368
+ },
1369
+ // Replay idempotente sem resposta recuperável. Pode persistir por 24 h (turno gravado sem
1370
+ // mensagem do assistente): retentar com o MESMO id bate no mesmo erro, por isso não é
1371
+ // retentável — o aviso diz a saída (novo `clientMessageId`).
1372
+ SERVICE_UNAVAILABLE: {
1373
+ kind: "server",
1374
+ message: "This message was already answered, but the stored answer could not be read; resend with a new clientMessageId.",
1375
+ retryable: false
1376
+ },
1377
+ // Defensivo: não há ponto de emissão in-stream observado no BE @ 9ec79cfc.
1378
+ RATE_LIMIT_EXCEEDED: { kind: "rate_limit", retryable: true },
1379
+ QUOTA_EXCEEDED: { kind: "quota_exceeded" },
1380
+ TRIAL_EXPIRED: { kind: "trial_expired" },
1381
+ NO_ACTIVE_SUBSCRIPTION: { kind: "no_subscription" }
1382
+ };
1383
+ function errorFromStreamChunk(chunk, ctx = {}) {
1384
+ const code = chunk.code ?? "AGENT_ERROR";
1385
+ const kind = classifyStreamErrorKind(code, chunk.timedOut);
1386
+ const known = chunk.timedOut === true ? void 0 : own(STREAM_ERROR_CODES, code);
1387
+ const serverMsg = chunk.message ? sanitizeApiError(chunk.message).message.replace(/<[^>]*>/g, "").slice(0, 300) : "";
1388
+ const message = known?.message ? known.keepServerMessage && serverMsg ? `${known.message} Server: ${serverMsg}` : known.message : serverMsg || code;
1389
+ return new ZihinAgentError(kind, message, {
1390
+ code,
1391
+ requestId: chunk.requestId,
1392
+ executionId: safeId(ctx.executionId),
1393
+ sessionId: safeId(ctx.sessionId),
1394
+ retryable: known?.retryable ?? false
1395
+ });
1396
+ }
1397
+ var FAILED_OUTCOMES = {
1398
+ ERROR: { kind: "server", message: "The agent turn failed on the server; its text is the engine failure notice, not an answer." },
1399
+ // `server`, não `aborted`: `aborted` é o cancelamento do PRÓPRIO chamador, que `onError` manda
1400
+ // filtrar — um cancelamento feito pelo operador sumiria junto.
1401
+ CANCELLED: { kind: "server", message: "The agent turn was cancelled on the server (by an operator or a platform guard)." },
1402
+ // Modo processo drenando/encerrando; o motor retoma depois. Nenhum canal atual o produz.
1403
+ SUSPENDED: { kind: "blocked", message: "The agent turn was suspended by the server and did not answer; retry later." },
1404
+ BUDGET_EXCEEDED: { kind: "blocked", message: "The agent's spending cap was reached; raise the agent budget to keep invoking it." },
1405
+ QUOTA_EXCEEDED: { kind: "quota_exceeded", message: "Your Zihin token quota is exhausted. Upgrade the plan or wait for the next cycle." },
1406
+ SESSION_BUSY: {
1407
+ kind: "blocked",
1408
+ message: "Another turn is still running in this session; retry after it finishes.",
1409
+ retryable: true
1410
+ },
1411
+ // Hoje a rota manda CONFLICT como `event: error` (não chega ao `done`); mapeado por defesa.
1412
+ CONFLICT: {
1413
+ kind: "blocked",
1414
+ message: "The declared consumer identity does not match the one already bound to this session."
1415
+ },
1416
+ // O provedor do modelo recusou por política (BE `4cea8d53`, 08/10/2026). O `content` é um texto
1417
+ // neutro FIXO da plataforma ("Não consigo ajudar com esse pedido…"), não uma resposta: o BE
1418
+ // registra a execução como erro. Publicá-lo como resposta era a falha silenciosa da 0.6.
1419
+ REFUSED: {
1420
+ kind: "blocked",
1421
+ message: "The model provider refused this request under its content policy; the agent did not answer."
1422
+ },
1423
+ // No `/stream` chega como `event: error` (ver STREAM_ERROR_CODES); no webhook sync e em
1424
+ // backends futuros pode vir como desfecho.
1425
+ CONTEXT_LIMIT: {
1426
+ kind: "blocked",
1427
+ message: "The conversation gathered more context than the model can process; send a smaller request or start a new session."
1428
+ }
1429
+ };
1430
+ function errorFromOutcome(outcome, ctx = {}) {
1431
+ const label = safeLabel(outcome);
1432
+ if (!label) return void 0;
1433
+ ctx = { ...ctx, executionId: safeId(ctx.executionId), sessionId: safeId(ctx.sessionId) };
1434
+ if (label === "TIMEOUT") {
1435
+ return new ZihinAgentError("timeout", "The agent turn timed out on the server.", { ...ctx, code: "TURN_TIMEOUT", retryable: false });
1436
+ }
1437
+ const failed = own(FAILED_OUTCOMES, label);
1438
+ if (!failed) return void 0;
1439
+ return new ZihinAgentError(failed.kind, failed.message, { ...ctx, code: label, retryable: failed.retryable ?? false });
1440
+ }
1441
+ var PAUSED_BLOCKS = /* @__PURE__ */ new Set(["agent_paused", "agent_archived"]);
1442
+ var ERROR_BLOCKS = {
1443
+ ownership_denied: {
1444
+ kind: "not_found",
1445
+ message: "Session not found for this API key \u2014 start a new conversation (omit sessionId) instead of reusing this one."
1446
+ },
1447
+ session_load_failed: { kind: "blocked", message: "The server could not load this conversation right now; retry shortly.", retryable: true },
1448
+ approval_resolve_failed: { kind: "blocked", message: "The server could not route the required approval right now; retry shortly.", retryable: true },
1449
+ approval_no_approver: { kind: "blocked", message: "This action requires approval, but the approval policy has no approver configured." },
1450
+ blocked_schedule: { kind: "blocked", message: "The agent is outside its allowed schedule (security policy)." },
1451
+ owner_inactive: { kind: "blocked", message: "The agent's owner is inactive; the agent cannot be invoked." }
1452
+ };
1453
+ function own(table, key) {
1454
+ return key !== void 0 && Object.prototype.hasOwnProperty.call(table, key) ? table[key] : void 0;
1455
+ }
1456
+ function errorFromDoneChunk(chunk, ctx = {}) {
1457
+ const meta = { executionId: ctx.executionId, sessionId: ctx.sessionId };
1458
+ const blockedReason = safeLabel(ctx.blocked);
1459
+ if (chunk.outcome === "TIMEOUT" || chunk.timedOut === true) {
1460
+ const clock = chunk.timeoutClock;
1461
+ const what = clock === "turn" ? "the turn deadline" : clock === "first_signal" ? "the model provider did not start responding in time" : clock === "idle" ? "the model provider stream went silent" : clock === "call" ? "the model call took too long" : "a server-side clock";
1462
+ return new ZihinAgentError("timeout", `The agent turn timed out (${what}).`, {
1463
+ ...meta,
1464
+ code: "TURN_TIMEOUT",
1465
+ retryable: false,
1466
+ timeoutClock: clock
1467
+ });
1468
+ }
1469
+ if (chunk.outcome === "PAUSED" && blockedReason && PAUSED_BLOCKS.has(blockedReason)) {
1470
+ return new ZihinAgentError("blocked", "The agent is paused or archived and did not answer.", {
1471
+ ...meta,
1472
+ code: "PAUSED",
1473
+ retryable: false
1474
+ });
1475
+ }
1476
+ if (chunk.outcome === "ERROR" && blockedReason) {
1477
+ const gate = own(ERROR_BLOCKS, blockedReason);
1478
+ return new ZihinAgentError(gate?.kind ?? "blocked", gate?.message ?? `The agent turn was blocked by the server (${blockedReason}).`, {
1479
+ ...meta,
1480
+ code: blockedReason.toUpperCase(),
1481
+ retryable: gate?.retryable ?? false
1482
+ });
1483
+ }
1484
+ const failed = own(FAILED_OUTCOMES, chunk.outcome);
1485
+ if (!failed) return void 0;
1486
+ return new ZihinAgentError(failed.kind, failed.message, {
1487
+ ...meta,
1488
+ code: chunk.outcome,
1489
+ retryable: failed.retryable ?? false
1490
+ });
1491
+ }
1492
+ async function invokeAgent(cfg, params) {
1493
+ const raw = [];
1494
+ const keepRaw = params.includeRaw === true;
1495
+ let responseChunk;
1496
+ let metricsChunk;
1497
+ let metadataChunk;
1498
+ let metadataSessionId;
1499
+ let mintedSessionId;
1500
+ let doneChunk;
1501
+ let blocked;
1502
+ let acceptedFields;
1503
+ let ignoredFields;
1504
+ let tokenBuffer = "";
1505
+ for await (const chunk of streamAgent(cfg, params)) {
1506
+ if (keepRaw) raw.push(chunk);
1507
+ if (chunk.type === "error") {
1508
+ throw errorFromStreamChunk(chunk, {
1509
+ executionId: metadataChunk?.executionId,
1510
+ sessionId: mintedSessionId ?? metadataSessionId ?? params.sessionId
1511
+ });
1512
+ }
1513
+ if (chunk.type === "response") responseChunk = chunk;
1514
+ else if (chunk.type === "metrics") metricsChunk = chunk;
1515
+ else if (chunk.type === "token") tokenBuffer += chunk.content;
1516
+ else if (chunk.type === "metadata") {
1517
+ metadataChunk = chunk;
1518
+ if (chunk.sessionId) metadataSessionId = chunk.sessionId;
1519
+ acceptedFields = chunk.acceptedFields;
1520
+ ignoredFields = chunk.ignoredFields;
1521
+ } else if (chunk.type === "session" && chunk.sessionId) mintedSessionId = chunk.sessionId;
1522
+ else if (chunk.type === "done") doneChunk = chunk;
1523
+ else if (chunk.type === "status" && chunk.blocked) blocked = chunk.blocked;
1524
+ }
1525
+ const sessionId = mintedSessionId ?? responseChunk?.sessionId ?? metadataSessionId ?? params.sessionId;
1526
+ const executionId = metadataChunk?.executionId ?? doneChunk?.executionId ?? responseChunk?.executionId ?? metricsChunk?.executionId;
1527
+ const ids = {
1528
+ clientMessageId: doneChunk?.clientMessageId ?? responseChunk?.clientMessageId ?? metadataChunk?.clientMessageId,
1529
+ idempotentReplay: doneChunk?.idempotentReplay ?? responseChunk?.idempotentReplay ?? metadataChunk?.idempotentReplay,
1530
+ userMessageId: doneChunk?.userMessageId ?? responseChunk?.userMessageId,
1531
+ assistantMessageId: doneChunk?.assistantMessageId ?? responseChunk?.assistantMessageId
1532
+ };
1533
+ const outcomeError = doneChunk && errorFromDoneChunk(doneChunk, { executionId, sessionId, blocked });
1534
+ if (outcomeError) throw outcomeError;
1535
+ const replaySession = responseChunk?.sessionId ?? metadataSessionId;
1536
+ if (ids.idempotentReplay && params.sessionId && replaySession && replaySession.toLowerCase() !== params.sessionId.toLowerCase()) {
1537
+ throw new ZihinAgentError(
1538
+ "config",
1539
+ "This `clientMessageId` was already used in a different session: the server replayed that session's answer. Use a new UUID per message (never a fixed value).",
1540
+ { code: "IDEMPOTENT_REPLAY_OTHER_SESSION", executionId, sessionId: params.sessionId, retryable: false }
1541
+ );
1542
+ }
1543
+ const cleanEnd = doneChunk !== void 0;
1544
+ if (!responseChunk) {
1545
+ if (cleanEnd) {
1546
+ throw new ZihinAgentError("protocol", "Stream ended (done) without a `response` event.");
1547
+ }
1548
+ if (tokenBuffer) {
1549
+ return {
1550
+ content: tokenBuffer,
1551
+ contentSource: "tokens",
1552
+ sessionId,
1553
+ usage: metricsChunk?.usage,
1554
+ model: metricsChunk?.model,
1555
+ iterations: metricsChunk?.iterations,
1556
+ executionTimeMs: metricsChunk?.executionTimeMs,
1557
+ executionId,
1558
+ ...ids,
1559
+ acceptedFields,
1560
+ ignoredFields,
1561
+ raw
1562
+ };
1563
+ }
1564
+ throw new ZihinAgentError("protocol", "Stream ended without a `response` event.");
1565
+ }
1566
+ return {
1567
+ content: responseChunk.content,
1568
+ contentSource: "response",
1569
+ sessionId,
1570
+ model: metricsChunk?.model,
1571
+ usage: metricsChunk?.usage,
1572
+ sources: responseChunk.sources,
1573
+ blocks: responseChunk.blocks,
1574
+ suggestions: responseChunk.suggestions,
1575
+ gated: responseChunk.gated,
1576
+ iterations: metricsChunk?.iterations,
1577
+ executionTimeMs: metricsChunk?.executionTimeMs,
1578
+ executionId,
1579
+ ...ids,
1580
+ outcome: doneChunk?.outcome,
1581
+ truncated: doneChunk?.truncated,
1582
+ finishReason: doneChunk?.finishReason,
1583
+ acceptedFields,
1584
+ ignoredFields,
1585
+ raw
1586
+ };
1587
+ }
1588
+
1589
+ // src/tasks.ts
1590
+ var MAX_TASK_PAYLOAD_BYTES = 64 * 1024;
1591
+ var SCHEDULE_PAST_TOLERANCE_MS = 6e4;
1592
+ function envelope(json) {
1593
+ const j = rec(json);
1594
+ return { data: rec(j.data ?? j), meta: rec(j.meta) };
1595
+ }
1596
+ function toIso(value, label) {
1597
+ if (value === void 0 || value === null || value === "") return void 0;
1598
+ const d = value instanceof Date ? value : new Date(value);
1599
+ if (Number.isNaN(d.getTime())) {
1600
+ throw new ZihinAgentError("config", `${label} must be a valid ISO 8601 date (got ${JSON.stringify(value)}).`);
1601
+ }
1602
+ return d.toISOString();
1603
+ }
1604
+ function mapTask(raw) {
1605
+ const t = rec(raw);
1606
+ const status = str(t.status);
1607
+ return {
1608
+ id: str(t.id) ?? "",
1609
+ agentId: str(t.agent_id),
1610
+ title: str(t.title),
1611
+ instruction: str(t.instruction),
1612
+ status: status ?? "unknown",
1613
+ // Mesma regra do Caminho B, e de propósito a MESMA função: status desconhecido conta como
1614
+ // terminal (o polling para em vez de girar), status ausente não (não lemos o envelope).
1615
+ terminal: status === void 0 ? false : isTerminalStatus(status),
1616
+ scheduledFor: str(t.scheduled_for),
1617
+ deadlineAt: str(t.deadline_at),
1618
+ parentTaskId: str(t.parent_task_id),
1619
+ rootTaskId: str(t.root_task_id),
1620
+ sessionId: str(t.session_id),
1621
+ createdAt: str(t.created_at),
1622
+ updatedAt: str(t.updated_at),
1623
+ raw
1624
+ };
1625
+ }
1626
+ function mapExecution(raw) {
1627
+ const e = rec(raw);
1628
+ return {
1629
+ id: str(e.id) ?? "",
1630
+ status: str(e.status) ?? "unknown",
1631
+ exitReason: str(e.exit_reason),
1632
+ modelUsed: str(e.model_used),
1633
+ totalCostUsd: num(e.total_cost_usd),
1634
+ executionTimeMs: num(e.execution_time_ms),
1635
+ iterations: num(e.iterations),
1636
+ startedAt: str(e.started_at),
1637
+ finishedAt: str(e.finished_at),
1638
+ raw
1639
+ };
1640
+ }
1641
+ async function delegateTask(cfg, params) {
1642
+ if (!params.agentId) throw new ZihinAgentError("config", "agentId is required.");
1643
+ if (!params.instruction) throw new ZihinAgentError("config", "instruction is required.");
1644
+ const scheduledFor = toIso(params.scheduledFor, "scheduledFor");
1645
+ const deadlineAt = toIso(params.deadlineAt, "deadlineAt");
1646
+ if (scheduledFor && Date.parse(scheduledFor) < Date.now() - SCHEDULE_PAST_TOLERANCE_MS) {
1647
+ throw new ZihinAgentError("config", "scheduledFor is in the past.");
1648
+ }
1649
+ if (deadlineAt && Date.parse(deadlineAt) <= Date.now() - SCHEDULE_PAST_TOLERANCE_MS) {
1650
+ throw new ZihinAgentError("config", "deadlineAt must be in the future.");
1651
+ }
1652
+ if (scheduledFor && deadlineAt && Date.parse(deadlineAt) <= Date.parse(scheduledFor)) {
1653
+ throw new ZihinAgentError("config", "deadlineAt must be after scheduledFor.");
1654
+ }
1655
+ if (params.payload !== void 0) {
1656
+ if (typeof params.payload !== "object" || params.payload === null || Array.isArray(params.payload)) {
1657
+ throw new ZihinAgentError("config", "payload must be an object.");
1658
+ }
1659
+ const bytes = toJsonBody(params.payload, "payload").length;
1660
+ if (bytes > MAX_TASK_PAYLOAD_BYTES) {
1661
+ throw new ZihinAgentError(
1662
+ "config",
1663
+ `payload exceeds ${MAX_TASK_PAYLOAD_BYTES / 1024}KB (got ${Math.ceil(bytes / 1024)}KB).`
1664
+ );
1665
+ }
1666
+ }
1667
+ const body = {
1668
+ agent_id: params.agentId,
1669
+ instruction: params.instruction
1670
+ };
1671
+ if (params.title !== void 0) body.title = params.title;
1672
+ if (params.payload !== void 0) body.payload = params.payload;
1673
+ if (scheduledFor) body.scheduled_for = scheduledFor;
1674
+ if (deadlineAt) body.deadline_at = deadlineAt;
1675
+ if (params.retryPolicy !== void 0) body.retry_policy = params.retryPolicy;
1676
+ if (params.notify !== void 0) body.notify = params.notify;
1677
+ if (params.parentTaskId) body.parent_task_id = params.parentTaskId;
1678
+ const json = await requestJson(cfg, {
1679
+ method: "POST",
1680
+ path: "/api/v1/tasks",
1681
+ body,
1682
+ // Criar uma task E dispará-la é efeito colateral: nunca repetir em erro de rede.
1683
+ policy: "side-effecting",
1684
+ signal: params.signal
1685
+ });
1686
+ const { data } = envelope(json);
1687
+ const task = mapTask(data.task ?? data);
1688
+ if (!task.id) {
1689
+ throw new ZihinAgentError("protocol", "delegateTask: response has no task id.");
1690
+ }
1691
+ return task;
1692
+ }
1693
+ async function listTasks(cfg, params = {}) {
1694
+ const qs = new URLSearchParams();
1695
+ if (params.live) qs.set("live", "true");
1696
+ if (params.status) qs.set("status", params.status);
1697
+ if (params.agentId) qs.set("agent_id", params.agentId);
1698
+ if (params.rootTaskId) qs.set("root_task_id", params.rootTaskId);
1699
+ if (params.limit !== void 0) qs.set("limit", String(params.limit));
1700
+ if (params.offset !== void 0) qs.set("offset", String(params.offset));
1701
+ const query = qs.toString();
1702
+ const json = await requestJson(cfg, {
1703
+ method: "GET",
1704
+ path: `/api/v1/tasks${query ? `?${query}` : ""}`,
1705
+ policy: "idempotent",
1706
+ signal: params.signal
1707
+ });
1708
+ const { data, meta } = envelope(json);
1709
+ const items = Array.isArray(data.items) ? data.items : [];
1710
+ return {
1711
+ items: items.map(mapTask),
1712
+ // ⚠️ `total` vive em `meta`, não no topo — o envelope novo aninhou os campos de paginação.
1713
+ total: num(meta.total),
1714
+ raw: json
1715
+ };
1716
+ }
1717
+ async function getTask(cfg, params) {
1718
+ if (!params.taskId) throw new ZihinAgentError("config", "taskId is required.");
1719
+ const json = await requestJson(cfg, {
1720
+ method: "GET",
1721
+ path: `/api/v1/tasks/${encodeURIComponent(params.taskId)}`,
1722
+ policy: "idempotent",
1723
+ signal: params.signal
1724
+ });
1725
+ const { data } = envelope(json);
1726
+ const taskRaw = data.task ?? (str(rec(data).id) ? data : void 0);
1727
+ if (!taskRaw || !str(rec(taskRaw).id)) {
1728
+ throw new ZihinAgentError("protocol", "getTask: response has no task.");
1729
+ }
1730
+ const executions = Array.isArray(data.executions) ? data.executions : [];
1731
+ const children = Array.isArray(data.children) ? data.children : [];
1732
+ return {
1733
+ task: mapTask(taskRaw),
1734
+ executions: executions.map(mapExecution),
1735
+ children: children.map(mapTask),
1736
+ approvals: Array.isArray(data.approvals) ? data.approvals : [],
1737
+ raw: json
1738
+ };
1739
+ }
1740
+ async function cancelTask(cfg, params) {
1741
+ if (!params.taskId) throw new ZihinAgentError("config", "taskId is required.");
1742
+ const json = await requestJson(cfg, {
1743
+ method: "POST",
1744
+ path: `/api/v1/tasks/${encodeURIComponent(params.taskId)}/cancel`,
1745
+ body: params.reason ? { reason: params.reason } : {},
1746
+ // Cancelar é side-effecting; repetir num socket caído bateria em 409 e confundiria o chamador.
1747
+ policy: "side-effecting",
1748
+ signal: params.signal
1749
+ });
1750
+ const { data } = envelope(json);
1751
+ const taskRaw = data.task ?? (str(rec(data).id) ? data : void 0);
1752
+ if (!taskRaw || !str(rec(taskRaw).id)) {
1753
+ throw new ZihinAgentError("protocol", "cancelTask: response has no task.");
1754
+ }
1755
+ return {
1756
+ task: mapTask(taskRaw),
1757
+ aborted: data.aborted === true,
1758
+ raw: json
1759
+ };
1760
+ }
1761
+
1762
+ // src/triggers.ts
1763
+ function queryExtractionBody(extraction, queryField) {
1764
+ if (!extraction) return { mode: "field", field: queryField ?? "message" };
1765
+ switch (extraction.mode) {
1766
+ case "field":
1767
+ if (!extraction.field) {
1768
+ throw new ZihinAgentError("config", 'queryExtraction.field is required for mode "field".');
1769
+ }
1770
+ return { mode: "field", field: extraction.field };
1771
+ case "template":
1772
+ if (!extraction.template) {
1773
+ throw new ZihinAgentError("config", 'queryExtraction.template is required for mode "template".');
1774
+ }
1775
+ return { mode: "template", template: extraction.template };
1776
+ case "full_body":
1777
+ return { mode: "full_body" };
1778
+ default:
1779
+ throw new ZihinAgentError("config", "Unknown queryExtraction.mode (use field|template|full_body).");
1780
+ }
1781
+ }
1782
+ async function createTrigger(cfg, params) {
1783
+ if (!params.agentId) throw new ZihinAgentError("config", "agentId is required.");
1784
+ if (!params.name) throw new ZihinAgentError("config", "name is required.");
1785
+ const mode = params.mode ?? "async";
1786
+ if (mode === "async" && !params.callbackUrl) {
1787
+ throw new ZihinAgentError("config", "callbackUrl is required for async triggers.");
1788
+ }
1789
+ if (params.callbackUrl) {
1790
+ const cb = validateBaseUrl(params.callbackUrl, { allowCustomDomains: true });
1791
+ if (!cb.valid) {
1792
+ throw new ZihinAgentError("config", `Invalid callbackUrl: ${cb.error}`);
1793
+ }
1794
+ }
1795
+ if (params.responseTransform && !params.callbackUrl) {
1796
+ throw new ZihinAgentError(
1797
+ "config",
1798
+ "responseTransform requires callbackUrl: the backend only reads it from trigger_config.execution.callback.response_transform, and a callback without a url is rejected."
1799
+ );
1800
+ }
1801
+ const execution = { mode };
1802
+ if (params.callbackUrl) {
1803
+ execution.callback = {
1804
+ url: params.callbackUrl,
1805
+ method: params.callbackMethod ?? "POST",
1806
+ ...params.responseTransform ? { response_transform: { mode: params.responseTransform } } : {}
1807
+ };
1808
+ }
1809
+ const body = {
1810
+ agent_id: params.agentId,
1811
+ name: params.name,
1812
+ trigger_type: "webhook",
1813
+ trigger_config: {
1814
+ query_extraction: queryExtractionBody(params.queryExtraction, params.queryField),
1815
+ auth: { type: params.dispatchAuth ?? "api_key" },
1816
+ execution
1817
+ }
1818
+ };
1819
+ const json = rec(
1820
+ await requestJson(cfg, { method: "POST", path: "/api/triggers", body, policy: "side-effecting", signal: params.signal })
1821
+ );
1822
+ const data = rec(json.data);
1823
+ const triggerId = str(data.id) ?? str(json.id);
1824
+ const webhookUrl = str(json.webhook_url) ?? (triggerId ? `${cfg.baseUrl}/api/triggers/webhook/${triggerId}` : void 0);
1825
+ if (!triggerId || !webhookUrl) {
1826
+ throw new ZihinAgentError("protocol", "createTrigger: missing trigger id / webhook url in response.");
1827
+ }
1828
+ return { triggerId, webhookUrl };
1829
+ }
1830
+ async function dispatchAgent(cfg, params) {
1831
+ if (!params.triggerId) throw new ZihinAgentError("config", "triggerId is required.");
1832
+ const res = await requestBuffered({
1833
+ method: "POST",
1834
+ url: `${cfg.baseUrl}/api/triggers/webhook/${encodeURIComponent(params.triggerId)}`,
1835
+ headers: buildHeaders(cfg.apiKey),
1836
+ body: toJsonBody(params.payload ?? {}, "payload"),
1837
+ timeoutMs: resolveTimeout(cfg, "dispatch"),
1838
+ maxRetries: cfg.maxRetries,
1839
+ policy: "side-effecting",
1840
+ // S1: nunca duplica disparo por erro de rede
1841
+ callerSignal: params.signal,
1842
+ fetchImpl: cfg.fetchImpl,
1843
+ runtime: cfg.runtime,
1844
+ onRetry: cfg.onRetry
1845
+ });
1846
+ if (!res.ok) {
1847
+ throw errorFromResponse(res.status, parseBody(res.text), { endpoint: res.endpoint, attempts: res.attempts });
1848
+ }
1849
+ const json = parseBody(res.text);
1850
+ const j = rec(json);
1851
+ const preTurnOutcome = safeLabel(j.outcome);
1852
+ if (j.success === false && preTurnOutcome && preTurnOutcome !== "ERROR") {
1853
+ const classified = errorFromOutcome(preTurnOutcome, {
1854
+ status: res.status,
1855
+ endpoint: res.endpoint,
1856
+ attempts: res.attempts,
1857
+ executionId: safeId(j.execution_id)
1858
+ });
1859
+ if (classified) throw classified;
1860
+ }
1861
+ if (j.success === false) {
1862
+ const detail = sanitizeApiError(str(j.error) ?? "").message;
1863
+ throw new ZihinAgentError("server", detail || "The agent run failed.", {
1864
+ status: res.status,
1865
+ endpoint: res.endpoint,
1866
+ attempts: res.attempts,
1867
+ executionId: safeId(j.execution_id),
1868
+ // Não é transitório: o turno rodou e falhou. Repetir gastaria outra execução.
1869
+ retryable: false
1870
+ });
1871
+ }
1872
+ const outcomeError = errorFromOutcome(str(j.outcome), {
1873
+ status: res.status,
1874
+ endpoint: res.endpoint,
1875
+ attempts: res.attempts,
1876
+ executionId: safeId(j.execution_id)
1877
+ });
1878
+ if (outcomeError) throw outcomeError;
1879
+ return {
1880
+ accepted: true,
1881
+ outcome: safeLabel(j.outcome),
1882
+ triggerId: str(j.trigger_id) ?? params.triggerId,
1883
+ agentId: str(j.agent_id),
1884
+ executionId: safeId(j.execution_id),
1885
+ agentResponse: str(j.agent_response),
1886
+ usage: parseUsage(j.usage),
1887
+ iterations: num(j.iterations),
1888
+ latencyMs: num(j.latency_ms),
1889
+ // Qual texto o BE extraiu do payload — é o campo que denuncia um `query_extraction` mal
1890
+ // configurado (mensagem vazia, ou o payload inteiro serializado virando a pergunta).
1891
+ queryExtracted: str(j.query_extracted),
1892
+ raw: json
1893
+ };
1894
+ }
1895
+ function webhookUrlFor(cfg, t) {
1896
+ if (str(t.webhook_url)) return str(t.webhook_url);
1897
+ const id = str(t.id);
1898
+ return t.trigger_type === "webhook" && id ? `${cfg.baseUrl}/api/triggers/webhook/${id}` : void 0;
1899
+ }
1900
+ function mapTriggerInfo(cfg, raw) {
1901
+ const t = rec(raw);
1902
+ return {
1903
+ id: str(t.id) ?? "",
1904
+ agentId: str(t.agent_id),
1905
+ name: str(t.name),
1906
+ description: str(t.description),
1907
+ triggerType: str(t.trigger_type),
1908
+ enabled: typeof t.enabled === "boolean" ? t.enabled : void 0,
1909
+ triggerCount: num(t.trigger_count),
1910
+ lastError: str(t.last_error),
1911
+ webhookUrl: webhookUrlFor(cfg, t)
1912
+ };
1913
+ }
1914
+ async function listTriggers(cfg, params = {}) {
1915
+ const qs = new URLSearchParams();
1916
+ if (params.agentId) qs.set("agentId", params.agentId);
1917
+ if (params.triggerType) qs.set("triggerType", params.triggerType);
1918
+ if (params.enabledOnly) qs.set("enabledOnly", "true");
1919
+ const query = qs.toString();
1920
+ const path = `/api/triggers${query ? `?${query}` : ""}`;
1921
+ const json = await requestJson(cfg, { method: "GET", path, policy: "idempotent", signal: params.signal });
1922
+ const list = Array.isArray(json) ? json : Array.isArray(rec(json).data) ? rec(json).data : [];
1923
+ return list.map((t) => mapTriggerInfo(cfg, t));
1924
+ }
1925
+ async function getTrigger(cfg, params) {
1926
+ if (!params.triggerId) throw new ZihinAgentError("config", "triggerId is required.");
1927
+ const json = await requestJson(cfg, {
1928
+ method: "GET",
1929
+ path: `/api/triggers/${encodeURIComponent(params.triggerId)}`,
1930
+ policy: "idempotent",
1931
+ signal: params.signal
1932
+ });
1933
+ return mapTriggerInfo(cfg, rec(json).data ?? json);
1934
+ }
1935
+ async function updateTrigger(cfg, params) {
1936
+ if (!params.triggerId) throw new ZihinAgentError("config", "triggerId is required.");
1937
+ if (params.callbackUrl) {
1938
+ const cb = validateBaseUrl(params.callbackUrl, { allowCustomDomains: true });
1939
+ if (!cb.valid) {
1940
+ throw new ZihinAgentError("config", `Invalid callbackUrl: ${cb.error}`);
1941
+ }
1942
+ }
1943
+ const body = {};
1944
+ if (params.name !== void 0) body.name = params.name;
1945
+ if (params.description !== void 0) body.description = params.description;
1946
+ if (params.enabled !== void 0) body.enabled = params.enabled;
1947
+ const touchesConfig = params.queryExtraction !== void 0 || params.callbackUrl !== void 0 || params.callbackMethod !== void 0 || params.responseTransform !== void 0 || params.mode !== void 0;
1948
+ if (touchesConfig) {
1949
+ const path = `/api/triggers/${encodeURIComponent(params.triggerId)}`;
1950
+ const currentJson = rec(
1951
+ await requestJson(cfg, { method: "GET", path, policy: "idempotent", signal: params.signal })
1952
+ );
1953
+ const current = rec(currentJson.data ?? currentJson);
1954
+ if (!current.trigger_config || typeof current.trigger_config !== "object" || Object.keys(rec(current.trigger_config)).length === 0) {
1955
+ throw new ZihinAgentError(
1956
+ "protocol",
1957
+ "updateTrigger: current trigger_config unavailable \u2014 refusing to overwrite it partially."
1958
+ );
1959
+ }
1960
+ const config = rec(current.trigger_config);
1961
+ const merged = { ...config };
1962
+ if (params.queryExtraction !== void 0) {
1963
+ merged.query_extraction = queryExtractionBody(params.queryExtraction, void 0);
1964
+ }
1965
+ const touchesCallback = params.callbackUrl !== void 0 || params.callbackMethod !== void 0 || params.responseTransform !== void 0;
1966
+ if (params.mode !== void 0 || touchesCallback) {
1967
+ const execution = rec(config.execution);
1968
+ const callback = rec(execution.callback);
1969
+ const newExecution = { ...execution };
1970
+ if (params.mode !== void 0) newExecution.mode = params.mode;
1971
+ if (touchesCallback) {
1972
+ const url = params.callbackUrl ?? str(callback.url);
1973
+ if (!url) {
1974
+ throw new ZihinAgentError(
1975
+ "config",
1976
+ params.responseTransform ? "responseTransform requires a callback: the backend only reads it from trigger_config.execution.callback.response_transform, and a callback without a url is rejected." : "callbackUrl is required: this trigger has no callback configured yet."
1977
+ );
1978
+ }
1979
+ newExecution.callback = {
1980
+ // Preserva o callback atual: trocar só a URL não pode apagar auth/headers/
1981
+ // body_template/content_type/response_transform. Antes este objeto era
1982
+ // SUBSTITUÍDO por { url, method } — a mesma perda silenciosa que o merge de
1983
+ // trigger_config acima existe para evitar, um nível mais fundo.
1984
+ ...callback,
1985
+ url,
1986
+ method: params.callbackMethod ?? str(callback.method) ?? "POST",
1987
+ ...params.responseTransform ? {
1988
+ response_transform: {
1989
+ ...rec(callback.response_transform),
1990
+ mode: params.responseTransform
1991
+ }
1992
+ } : {}
1993
+ };
1994
+ }
1995
+ if (newExecution.mode === "async" && !rec(newExecution.callback).url) {
1996
+ throw new ZihinAgentError("config", "callbackUrl is required for async triggers.");
1997
+ }
1998
+ merged.execution = newExecution;
1999
+ }
2000
+ body.trigger_config = merged;
2001
+ }
2002
+ if (Object.keys(body).length === 0) {
2003
+ throw new ZihinAgentError("config", "updateTrigger: no fields to update.");
2004
+ }
2005
+ const json = await requestJson(cfg, {
2006
+ method: "PATCH",
2007
+ path: `/api/triggers/${encodeURIComponent(params.triggerId)}`,
2008
+ body,
2009
+ // PATCH aqui é replay-safe: reaplicar os mesmos valores produz o mesmo estado.
2010
+ policy: "idempotent",
2011
+ signal: params.signal
2012
+ });
2013
+ return mapTriggerInfo(cfg, rec(json).data ?? json);
2014
+ }
2015
+ async function deleteTrigger(cfg, params) {
2016
+ if (!params.triggerId) throw new ZihinAgentError("config", "triggerId is required.");
2017
+ const res = await requestBuffered({
2018
+ method: "DELETE",
2019
+ url: `${cfg.baseUrl}/api/triggers/${encodeURIComponent(params.triggerId)}`,
2020
+ headers: buildHeaders(cfg.apiKey),
2021
+ timeoutMs: resolveTimeout(cfg, "short"),
2022
+ maxRetries: cfg.maxRetries,
2023
+ policy: "idempotent",
2024
+ // DELETE é idempotente
2025
+ callerSignal: params.signal,
2026
+ fetchImpl: cfg.fetchImpl,
2027
+ runtime: cfg.runtime,
2028
+ onRetry: cfg.onRetry
2029
+ });
2030
+ if (!res.ok) {
2031
+ throw errorFromResponse(res.status, parseBody(res.text), { endpoint: res.endpoint, attempts: res.attempts });
2032
+ }
2033
+ const j = rec(parseBody(res.text));
2034
+ return { deleted: j.deleted === true || res.status === 200 };
2035
+ }
2036
+ async function findOrCreateTrigger(cfg, params) {
2037
+ if (!params.agentId) throw new ZihinAgentError("config", "agentId is required.");
2038
+ if (!params.name) throw new ZihinAgentError("config", "name is required.");
2039
+ const existing = await listTriggers(cfg, { agentId: params.agentId, signal: params.signal });
2040
+ const match = existing.find((t) => t.name === params.name);
2041
+ if (match && match.id) {
2042
+ const webhookUrl = match.webhookUrl ?? `${cfg.baseUrl}/api/triggers/webhook/${match.id}`;
2043
+ return { triggerId: match.id, webhookUrl, created: false };
2044
+ }
2045
+ const ref = await createTrigger(cfg, params);
2046
+ return { ...ref, created: true };
2047
+ }
2048
+ async function getExecution(cfg, params) {
2049
+ if (!params.triggerId || !params.executionId) {
2050
+ throw new ZihinAgentError("config", "triggerId and executionId are required.");
2051
+ }
2052
+ const path = `/api/triggers/${encodeURIComponent(params.triggerId)}/executions/${encodeURIComponent(
2053
+ params.executionId
2054
+ )}`;
2055
+ const json = rec(await requestJson(cfg, { method: "GET", path, policy: "idempotent", signal: params.signal }));
2056
+ const data = rec(json.data);
2057
+ const exec = rec(data.execution ?? json.execution);
2058
+ const metrics = rec(data.metrics ?? json.metrics);
2059
+ const rawStatus = str(exec.status);
2060
+ return {
2061
+ executionId: str(exec.id) ?? params.executionId,
2062
+ status: rawStatus ?? "unknown",
2063
+ terminal: rawStatus === void 0 ? false : isTerminalStatus(rawStatus),
2064
+ startedAt: str(exec.started_at),
2065
+ finishedAt: str(exec.finished_at),
2066
+ executionTimeMs: num(exec.execution_time_ms),
2067
+ usage: parseUsage(metrics.usage ?? metrics) ?? parseUsage(exec.usage),
2068
+ result: str(exec.agent_response) ?? str(exec.result),
2069
+ raw: json
2070
+ };
2071
+ }
2072
+
2073
+ // src/client.ts
2074
+ var ZihinAgentClient = class {
2075
+ constructor(config) {
2076
+ this.cfg = resolveConfig(config);
2077
+ }
2078
+ /** Base URL efetiva (após validação SSRF). */
2079
+ get baseUrl() {
2080
+ return this.cfg.baseUrl;
2081
+ }
2082
+ /** Notifica `config.onError` (falha definitiva); o callback nunca derruba o fluxo. */
2083
+ report(err) {
2084
+ if (this.cfg.onError && err instanceof ZihinAgentError) {
2085
+ try {
2086
+ this.cfg.onError(err);
2087
+ } catch {
2088
+ }
2089
+ }
2090
+ }
2091
+ guard(p) {
2092
+ return p.catch((err) => {
2093
+ this.report(err);
2094
+ throw err;
2095
+ });
2096
+ }
2097
+ // ---- Caminho A: direto (SSE) ----
2098
+ /** Invoca o agente e retorna o resultado final consolidado (response + metrics). */
2099
+ invokeAgent(params) {
2100
+ return this.guard(invokeAgent(this.cfg, params));
2101
+ }
2102
+ /** Emite deltas ao vivo do agente (async iterator). */
2103
+ async *streamAgent(params) {
2104
+ try {
2105
+ yield* streamAgent(this.cfg, params);
2106
+ } catch (err) {
2107
+ this.report(err);
2108
+ throw err;
2109
+ }
2110
+ }
2111
+ // ---- Caminho B: triggers (async/callback) ----
2112
+ /** Provisiona (1x) um trigger de webhook ligado a um agente. */
2113
+ createTrigger(params) {
2114
+ return this.guard(createTrigger(this.cfg, params));
2115
+ }
2116
+ /** Reaproveita (por agente+nome) ou cria um trigger — evita duplicação. */
2117
+ findOrCreateTrigger(params) {
2118
+ return this.guard(findOrCreateTrigger(this.cfg, params));
2119
+ }
2120
+ /** Lista os triggers do tenant (filtros opcionais). */
2121
+ listTriggers(params = {}) {
2122
+ return this.guard(listTriggers(this.cfg, params));
2123
+ }
2124
+ /** Obtém um trigger pelo id (inclui webhookUrl). */
2125
+ getTrigger(params) {
2126
+ return this.guard(getTrigger(this.cfg, params));
2127
+ }
2128
+ /** Atualiza um trigger (name/description/enabled/extração/callback) via PATCH. */
2129
+ updateTrigger(params) {
2130
+ return this.guard(updateTrigger(this.cfg, params));
2131
+ }
2132
+ /** Remove um trigger (requer key admin/owner). */
2133
+ deleteTrigger(params) {
2134
+ return this.guard(deleteTrigger(this.cfg, params));
2135
+ }
2136
+ /** Dispara um trigger: 202 (async) ou JSON imediato (sync). */
2137
+ dispatchAgent(params) {
2138
+ return this.guard(dispatchAgent(this.cfg, params));
2139
+ }
2140
+ /** Consulta o resultado de uma execução disparada por trigger (polling). */
2141
+ getExecution(params) {
2142
+ return this.guard(getExecution(this.cfg, params));
2143
+ }
2144
+ // ---- Caminho C: tasks (delegação assíncrona durável) ----
2145
+ /**
2146
+ * Delega uma tarefa a um agente (requer key owner/admin). Sem `scheduledFor`, o BE dispara na
2147
+ * hora fora do request e devolve só o handle — acompanhe com `getTask`.
2148
+ */
2149
+ delegateTask(params) {
2150
+ return this.guard(delegateTask(this.cfg, params));
2151
+ }
2152
+ /** Board de tasks do tenant (requer key owner/admin/editor). */
2153
+ listTasks(params = {}) {
2154
+ return this.guard(listTasks(this.cfg, params));
2155
+ }
2156
+ /** Dossiê de uma task: task + execuções + filhos + aprovações. */
2157
+ getTask(params) {
2158
+ return this.guard(getTask(this.cfg, params));
2159
+ }
2160
+ /** Cancela uma task (requer key owner/admin); derruba o turno em voo, se houver. */
2161
+ cancelTask(params) {
2162
+ return this.guard(cancelTask(this.cfg, params));
2163
+ }
2164
+ // ---- Auxiliar ----
2165
+ /** Lista os agentes do tenant (dropdown). */
2166
+ listAgents(opts = {}) {
2167
+ return this.guard(listAgents(this.cfg, opts));
2168
+ }
2169
+ };
2170
+ function createClient(config) {
2171
+ return new ZihinAgentClient(config);
2172
+ }
2173
+
2174
+ // src/signing.ts
2175
+ function loadNodeCrypto() {
2176
+ const proc = globalThis.process;
2177
+ if (typeof proc?.getBuiltinModule === "function") {
2178
+ return proc.getBuiltinModule("node:crypto");
2179
+ }
2180
+ try {
2181
+ return __require("crypto");
2182
+ } catch {
2183
+ throw new ZihinAgentError(
2184
+ "config",
2185
+ "signRequest requires Node.js crypto, unavailable in this runtime (the ESM build needs Node >=20.16)."
2186
+ );
2187
+ }
2188
+ }
2189
+ function signRequest(payload, secretKey, timestamp) {
2190
+ const { createHmac, randomBytes } = loadNodeCrypto();
2191
+ const ts = timestamp || Date.now();
2192
+ const nonce = randomBytes(16).toString("hex");
2193
+ const message = `${ts}.${nonce}.${payload}`;
2194
+ const signature = createHmac("sha256", secretKey).update(message).digest("hex");
2195
+ return {
2196
+ "X-Zihin-Signature": `sha256=${signature}`,
2197
+ "X-Zihin-Timestamp": ts.toString(),
2198
+ "X-Zihin-Nonce": nonce
2199
+ };
2200
+ }
2201
+
2202
+ export {
2203
+ validateBaseUrl,
2204
+ DEFAULT_LIMITS,
2205
+ validatePromptSize,
2206
+ sanitizeApiError,
2207
+ validateApiKeyFormat,
2208
+ ZihinAgentError,
2209
+ isCommercialBlock,
2210
+ OPERATION_TIMEOUT_MS,
2211
+ DEADLINE_GRACE_MS,
2212
+ classifyStreamErrorKind,
2213
+ errorFromStreamChunk,
2214
+ errorFromOutcome,
2215
+ errorFromDoneChunk,
2216
+ MAX_TASK_PAYLOAD_BYTES,
2217
+ ZihinAgentClient,
2218
+ createClient,
2219
+ signRequest
2220
+ };