@zihin/agent-client 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs ADDED
@@ -0,0 +1,1276 @@
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 str(x) {
17
+ return typeof x === "string" ? x : void 0;
18
+ }
19
+ function num(x) {
20
+ return typeof x === "number" ? x : void 0;
21
+ }
22
+ function parseUsage(usageRaw) {
23
+ const u = rec(usageRaw);
24
+ if (Object.keys(u).length === 0) return void 0;
25
+ return {
26
+ inputTokens: num(u.input_tokens) ?? num(u.prompt_tokens),
27
+ outputTokens: num(u.output_tokens) ?? num(u.completion_tokens),
28
+ totalTokens: num(u.total_tokens),
29
+ costUsd: num(u.cost_usd)
30
+ };
31
+ }
32
+
33
+ // src/security.ts
34
+ var PRIVATE_IP_PATTERNS = [
35
+ /^127\./,
36
+ // 127.0.0.0/8 (localhost)
37
+ /^10\./,
38
+ // 10.0.0.0/8 (private)
39
+ /^172\.(1[6-9]|2[0-9]|3[0-1])\./,
40
+ // 172.16.0.0/12 (private)
41
+ /^192\.168\./,
42
+ // 192.168.0.0/16 (private)
43
+ /^169\.254\./,
44
+ // 169.254.0.0/16 (link-local)
45
+ /^0\./
46
+ // 0.0.0.0/8
47
+ ];
48
+ function parseIpv6(host) {
49
+ if (!host.includes(":")) return null;
50
+ let text = host;
51
+ const words = [];
52
+ const v4 = /(\d{1,3}(?:\.\d{1,3}){3})$/.exec(text);
53
+ let tail = [];
54
+ if (v4) {
55
+ const octets = v4[1].split(".").map(Number);
56
+ if (octets.some((o) => !Number.isInteger(o) || o < 0 || o > 255)) return null;
57
+ tail = [octets[0] << 8 | octets[1], octets[2] << 8 | octets[3]];
58
+ text = text.slice(0, v4.index).replace(/:$/, "") || "::";
59
+ }
60
+ const halves = text.split("::");
61
+ if (halves.length > 2) return null;
62
+ const toWords = (part) => {
63
+ if (!part) return [];
64
+ const out = [];
65
+ for (const group of part.split(":")) {
66
+ if (!/^[0-9a-f]{1,4}$/i.test(group)) return null;
67
+ out.push(parseInt(group, 16));
68
+ }
69
+ return out;
70
+ };
71
+ const head = toWords(halves[0]);
72
+ const rest = halves.length === 2 ? toWords(halves[1]) : [];
73
+ if (!head || !rest) return null;
74
+ if (halves.length === 2) {
75
+ const fill = 8 - (head.length + rest.length + tail.length);
76
+ if (fill < 0) return null;
77
+ words.push(...head, ...Array(fill).fill(0), ...rest, ...tail);
78
+ } else {
79
+ words.push(...head, ...tail);
80
+ }
81
+ return words.length === 8 ? words : null;
82
+ }
83
+ function isPrivateIpv6(words) {
84
+ const [w0, w1, w2, w3, w4, w5] = words;
85
+ const allZero = words.every((w) => w === 0);
86
+ if (allZero) return true;
87
+ if (words.slice(0, 7).every((w) => w === 0) && words[7] === 1) return true;
88
+ if (w0 === 0 && w1 === 0 && w2 === 0 && w3 === 0 && w4 === 0 && (w5 === 0 || w5 === 65535)) {
89
+ const v4 = `${words[6] >> 8}.${words[6] & 255}.${words[7] >> 8}.${words[7] & 255}`;
90
+ return PRIVATE_IP_PATTERNS.some((p) => p.test(v4));
91
+ }
92
+ if ((w0 >> 8 & 254) === 252) return true;
93
+ if ((w0 & 65472) === 65152) return true;
94
+ if ((w0 & 65472) === 65216) return true;
95
+ return false;
96
+ }
97
+ function bareHostname(hostname) {
98
+ const bare = hostname.startsWith("[") && hostname.endsWith("]") ? hostname.slice(1, -1) : hostname;
99
+ return bare.length > 1 && bare.endsWith(".") ? bare.replace(/\.+$/, "") : bare;
100
+ }
101
+ var BLOCKED_HOSTNAMES = [
102
+ "localhost",
103
+ "localtest.me",
104
+ "metadata.google.internal",
105
+ // GCP metadata
106
+ "169.254.169.254",
107
+ // AWS/Azure/GCP metadata
108
+ "metadata.azure.com"
109
+ ];
110
+ var ALLOWED_ZIHIN_DOMAINS = [
111
+ "llm.zihin.ai",
112
+ "api.zihin.ai",
113
+ "staging.llm.zihin.ai",
114
+ "dev.llm.zihin.ai"
115
+ ];
116
+ function validateBaseUrl(url, options = {}) {
117
+ if (!url || url.trim() === "") {
118
+ return { valid: true, sanitizedUrl: "https://llm.zihin.ai" };
119
+ }
120
+ try {
121
+ const parsed = new URL(url);
122
+ if (parsed.hostname.length > 1 && parsed.hostname.endsWith(".")) {
123
+ parsed.hostname = parsed.hostname.replace(/\.+$/, "");
124
+ }
125
+ const hostname = bareHostname(parsed.hostname);
126
+ const isLocalhost = hostname === "localhost" || hostname === "127.0.0.1" || hostname === "::1";
127
+ if (isLocalhost && options.allowLocalhost) {
128
+ const sanitizedUrl2 = parsed.origin + parsed.pathname.replace(/\/+$/, "");
129
+ return { valid: true, sanitizedUrl: sanitizedUrl2 };
130
+ }
131
+ if (!options.allowHttp && parsed.protocol !== "https:") {
132
+ return {
133
+ valid: false,
134
+ error: "Only HTTPS URLs are allowed for security reasons"
135
+ };
136
+ }
137
+ const ipv6 = parseIpv6(hostname);
138
+ if (ipv6) {
139
+ if (isPrivateIpv6(ipv6)) {
140
+ return { valid: false, error: "Private IP addresses are not allowed" };
141
+ }
142
+ } else {
143
+ for (const pattern of PRIVATE_IP_PATTERNS) {
144
+ if (pattern.test(hostname)) {
145
+ return { valid: false, error: "Private IP addresses are not allowed" };
146
+ }
147
+ }
148
+ }
149
+ for (const blocked of BLOCKED_HOSTNAMES) {
150
+ if (blocked.startsWith("*")) {
151
+ const suffix = blocked.slice(1);
152
+ if (hostname.endsWith(suffix)) {
153
+ return { valid: false, error: "This hostname is not allowed" };
154
+ }
155
+ } else if (hostname === blocked || hostname.endsWith("." + blocked)) {
156
+ return { valid: false, error: "This hostname is not allowed" };
157
+ }
158
+ }
159
+ if (!options.allowCustomDomains) {
160
+ const isAllowed = ALLOWED_ZIHIN_DOMAINS.some(
161
+ (domain) => hostname === domain || hostname.endsWith("." + domain)
162
+ );
163
+ if (!isAllowed) {
164
+ return {
165
+ valid: false,
166
+ error: `Only Zihin domains are allowed. Use 'allowCustomBaseUrl' for custom endpoints.`
167
+ };
168
+ }
169
+ }
170
+ const sanitizedUrl = parsed.origin + parsed.pathname.replace(/\/+$/, "");
171
+ return { valid: true, sanitizedUrl };
172
+ } catch {
173
+ return { valid: false, error: "Invalid URL format" };
174
+ }
175
+ }
176
+ var DEFAULT_LIMITS = {
177
+ maxPromptBytes: 100 * 1024,
178
+ // 100KB
179
+ maxMessageCount: 100,
180
+ maxSingleMessageBytes: 50 * 1024,
181
+ // 50KB
182
+ maxTotalPayloadBytes: 500 * 1024
183
+ // 500KB
184
+ };
185
+ function validatePromptSize(prompt, limits = {}) {
186
+ const effectiveLimits = { ...DEFAULT_LIMITS, ...limits };
187
+ const bytes = utf8Len(prompt);
188
+ if (bytes > effectiveLimits.maxPromptBytes) {
189
+ return {
190
+ valid: false,
191
+ error: `Prompt size (${Math.round(bytes / 1024)}KB) exceeds maximum allowed (${Math.round(
192
+ effectiveLimits.maxPromptBytes / 1024
193
+ )}KB)`,
194
+ details: { promptBytes: bytes }
195
+ };
196
+ }
197
+ return { valid: true, details: { promptBytes: bytes } };
198
+ }
199
+ var SENSITIVE_PATTERNS = [
200
+ /zhn_(?:live|test|dev)_[a-zA-Z0-9]+/g,
201
+ // API keys
202
+ /Bearer\s+[a-zA-Z0-9._-]+/gi,
203
+ // Bearer tokens
204
+ /at\s+[\w./<>]+\s*\(.*:\d+:\d+\)/g,
205
+ // Stack trace lines
206
+ /^\s+at\s+.+$/gm,
207
+ // Stack traces
208
+ /Error:\s*.*\n\s+at/g
209
+ // Error stack beginnings
210
+ ];
211
+ var ERROR_CODE_MAP = {
212
+ RATE_LIMIT_EXCEEDED: "Rate limit exceeded. Please wait before retrying.",
213
+ UNAUTHORIZED: "Authentication failed. Please check your API credentials.",
214
+ FORBIDDEN: "This API key lacks permission for this action. Use an admin/editor key (member keys cannot create triggers or read telemetry).",
215
+ INVALID_MODEL: "The specified model is not available.",
216
+ INTERNAL_ERROR: "An internal error occurred. Please try again later.",
217
+ TIMEOUT: "Request timed out. Try increasing the timeout.",
218
+ INVALID_REQUEST: "The request was malformed. Please check your input.",
219
+ INSUFFICIENT_CREDITS: "Insufficient credits. Please check your account balance.",
220
+ CONTEXT_LENGTH_EXCEEDED: "The input exceeds the model context length limit.",
221
+ AGENT_NOT_FOUND: "Agent not found or not active for this tenant."
222
+ };
223
+ function sanitizeApiError(error) {
224
+ let message = "An unexpected error occurred";
225
+ let code;
226
+ let retryable = false;
227
+ let retryAfter;
228
+ if (typeof error === "object" && error !== null) {
229
+ const err = error;
230
+ const nested = err.error && typeof err.error === "object" ? err.error : void 0;
231
+ const codeRaw = nested?.code ?? (typeof err.error === "string" ? err.error : err.code);
232
+ code = typeof codeRaw === "string" ? codeRaw : void 0;
233
+ const rawMessage = nested?.message ?? err.message;
234
+ if (code && ERROR_CODE_MAP[code]) {
235
+ message = ERROR_CODE_MAP[code];
236
+ } else if (typeof rawMessage === "string" && rawMessage) {
237
+ message = rawMessage;
238
+ }
239
+ if (code === "RATE_LIMIT_EXCEEDED" || code === "TIMEOUT") {
240
+ retryable = true;
241
+ retryAfter = err.retryAfter || 60;
242
+ }
243
+ if (message.length > 2e3) message = message.slice(0, 2e3);
244
+ for (const pattern of SENSITIVE_PATTERNS) {
245
+ message = message.replace(pattern, "[REDACTED]");
246
+ }
247
+ } else if (typeof error === "string") {
248
+ message = error;
249
+ if (message.length > 2e3) message = message.slice(0, 2e3);
250
+ for (const pattern of SENSITIVE_PATTERNS) {
251
+ message = message.replace(pattern, "[REDACTED]");
252
+ }
253
+ }
254
+ if (message.length > 500) {
255
+ message = message.substring(0, 500) + "...";
256
+ }
257
+ return { message, code, retryable, retryAfter };
258
+ }
259
+ var API_KEY_PATTERN = /^zhn_(live|test|dev)_[a-zA-Z0-9]{20,}$/;
260
+ function validateApiKeyFormat(apiKey) {
261
+ if (!apiKey || apiKey.trim() === "") {
262
+ return { valid: false, error: "API key is required" };
263
+ }
264
+ const trimmed = apiKey.trim();
265
+ if (!API_KEY_PATTERN.test(trimmed)) {
266
+ return {
267
+ valid: false,
268
+ error: "Invalid API key format. Expected: zhn_live_xxx, zhn_test_xxx or zhn_dev_xxx"
269
+ };
270
+ }
271
+ const environment = trimmed.startsWith("zhn_live_") ? "live" : trimmed.startsWith("zhn_test_") ? "test" : "dev";
272
+ return { valid: true, environment };
273
+ }
274
+
275
+ // src/errors.ts
276
+ var ZihinAgentError = class _ZihinAgentError extends Error {
277
+ constructor(kind, message, opts = {}) {
278
+ super(buildMessage(message, opts));
279
+ this.name = "ZihinAgentError";
280
+ this.kind = kind;
281
+ this.status = opts.status;
282
+ this.code = opts.code;
283
+ this.requestId = opts.requestId;
284
+ this.attempts = opts.attempts;
285
+ this.endpoint = opts.endpoint;
286
+ this.retryable = opts.retryable ?? (kind === "rate_limit" || kind === "server" || kind === "network");
287
+ Object.setPrototypeOf(this, _ZihinAgentError.prototype);
288
+ }
289
+ };
290
+ function buildMessage(base, opts) {
291
+ let s = base;
292
+ if (opts.endpoint) s += ` [${opts.endpoint}]`;
293
+ if (opts.attempts && opts.attempts > 1) s += ` (after ${opts.attempts} attempts)`;
294
+ if (opts.requestId) s += ` (request_id: ${opts.requestId})`;
295
+ return s;
296
+ }
297
+ function extractRequestId(body) {
298
+ const b = rec(body);
299
+ return str(b.request_id) ?? str(b.requestId) ?? str(rec(b.error).request_id);
300
+ }
301
+ function errorFromResponse(status, body, meta = {}) {
302
+ const sanitized = sanitizeApiError(body);
303
+ const message = sanitized.message;
304
+ const code = sanitized.code;
305
+ const base = { status, code, requestId: extractRequestId(body), ...meta };
306
+ if (status === 401) return new ZihinAgentError("auth", message || "Authentication failed.", base);
307
+ if (status === 403)
308
+ return new ZihinAgentError(
309
+ "auth",
310
+ message || "This API key lacks permission. Use an admin/editor key (member keys cannot create triggers or read telemetry).",
311
+ base
312
+ );
313
+ if (status === 404) return new ZihinAgentError("not_found", message || "Resource not found.", base);
314
+ if (status === 429)
315
+ return new ZihinAgentError("rate_limit", message || "Rate limit exceeded.", { ...base, retryable: true });
316
+ if (status >= 500)
317
+ return new ZihinAgentError("server", message || `Server error (${status}).`, { ...base, retryable: true });
318
+ return new ZihinAgentError("unknown", message || `Request failed (${status}).`, base);
319
+ }
320
+ var NON_RETRYABLE_NET = /^(ENOTFOUND|EAI_AGAIN|CERT_|ERR_TLS|UNABLE_TO_VERIFY|DEPTH_ZERO)/;
321
+ function errorFromThrow(err, timeoutMs, meta = {}) {
322
+ if (err instanceof ZihinAgentError) return err;
323
+ const name = err?.name;
324
+ if (name === "TimeoutError") {
325
+ return new ZihinAgentError("timeout", `Request timed out after ${timeoutMs ?? "?"}ms.`, {
326
+ ...meta,
327
+ retryable: false
328
+ });
329
+ }
330
+ if (name === "AbortError") {
331
+ return new ZihinAgentError("aborted", "Request was aborted by the caller.", { ...meta, retryable: false });
332
+ }
333
+ const cause = err?.cause;
334
+ const causeCode = cause?.code;
335
+ const sanitized = sanitizeApiError(err);
336
+ const detail = causeCode || sanitized.message || "Network error";
337
+ const retryable = causeCode ? !NON_RETRYABLE_NET.test(causeCode) : true;
338
+ return new ZihinAgentError("network", `Network error: ${detail}`, { ...meta, retryable });
339
+ }
340
+
341
+ // src/http.ts
342
+ var DEFAULT_BASE_URL = "https://llm.zihin.ai";
343
+ var DEFAULT_TIMEOUT_MS = 12e4;
344
+ var DEFAULT_MAX_RETRIES = 2;
345
+ var DEFAULT_MAX_EVENT_BYTES = 1 * 1024 * 1024;
346
+ var DEFAULT_MAX_TOTAL_BYTES = 64 * 1024 * 1024;
347
+ function resolveFetch(injected) {
348
+ const f = injected ?? globalThis.fetch;
349
+ if (typeof f !== "function") {
350
+ throw new ZihinAgentError("config", "Global fetch is unavailable in this runtime.");
351
+ }
352
+ return f;
353
+ }
354
+ function resolveConfig(config) {
355
+ const keyCheck = validateApiKeyFormat(config.apiKey ?? "");
356
+ if (!keyCheck.valid) {
357
+ throw new ZihinAgentError("config", keyCheck.error || "Invalid API key.");
358
+ }
359
+ const urlCheck = validateBaseUrl(config.baseUrl ?? "", {
360
+ allowCustomDomains: config.allowCustomBaseUrl,
361
+ allowHttp: config.allowHttp,
362
+ allowLocalhost: config.allowHttp
363
+ });
364
+ if (!urlCheck.valid) {
365
+ throw new ZihinAgentError("config", urlCheck.error || "Invalid base URL.");
366
+ }
367
+ if (typeof (config.fetch ?? globalThis.fetch) !== "function") {
368
+ throw new ZihinAgentError(
369
+ "config",
370
+ "Global fetch is unavailable in this runtime. Use Node >=18 or pass a `fetch` implementation in the client config."
371
+ );
372
+ }
373
+ return {
374
+ apiKey: config.apiKey.trim(),
375
+ baseUrl: urlCheck.sanitizedUrl || DEFAULT_BASE_URL,
376
+ timeoutMs: config.timeoutMs ?? DEFAULT_TIMEOUT_MS,
377
+ maxRetries: config.maxRetries ?? DEFAULT_MAX_RETRIES,
378
+ maxStreamEventBytes: config.maxStreamEventBytes ?? DEFAULT_MAX_EVENT_BYTES,
379
+ maxStreamTotalBytes: config.maxStreamTotalBytes ?? DEFAULT_MAX_TOTAL_BYTES,
380
+ fetchImpl: config.fetch,
381
+ onRetry: config.onRetry,
382
+ onError: config.onError
383
+ };
384
+ }
385
+ function endpointLabel(method, url) {
386
+ try {
387
+ const u = new URL(url);
388
+ return `${method} ${u.host}${u.pathname}`;
389
+ } catch {
390
+ return method;
391
+ }
392
+ }
393
+ function retryAfterMs(headers, capMs = 3e4) {
394
+ const raw = headers.get("retry-after");
395
+ if (!raw) return void 0;
396
+ const secs = Number(raw);
397
+ if (Number.isFinite(secs)) return Math.min(Math.max(0, secs * 1e3), capMs);
398
+ const when = Date.parse(raw);
399
+ if (Number.isFinite(when)) return Math.min(Math.max(0, when - Date.now()), capMs);
400
+ return void 0;
401
+ }
402
+ function buildHeaders(apiKey, extra = {}) {
403
+ return {
404
+ "Content-Type": "application/json",
405
+ Accept: "application/json",
406
+ "X-Api-Key": apiKey,
407
+ ...extra
408
+ };
409
+ }
410
+ function delay(ms, signal) {
411
+ return new Promise((resolve, reject) => {
412
+ if (signal?.aborted) {
413
+ reject(new ZihinAgentError("aborted", "Request was aborted by the caller."));
414
+ return;
415
+ }
416
+ const onAbort = () => {
417
+ clearTimeout(timer);
418
+ reject(new ZihinAgentError("aborted", "Request was aborted by the caller."));
419
+ };
420
+ const timer = setTimeout(() => {
421
+ if (signal) signal.removeEventListener("abort", onAbort);
422
+ resolve();
423
+ }, ms);
424
+ if (signal) signal.addEventListener("abort", onAbort, { once: true });
425
+ });
426
+ }
427
+ function backoffMs(attempt) {
428
+ const base = Math.min(1e3 * 2 ** attempt, 1e4);
429
+ return base / 2 + Math.random() * (base / 2);
430
+ }
431
+ function createDeadline(timeoutMs, callerSignal) {
432
+ const controller = new AbortController();
433
+ let timedOut = false;
434
+ let abortedByCaller = false;
435
+ const timer = setTimeout(() => {
436
+ timedOut = true;
437
+ controller.abort();
438
+ }, timeoutMs);
439
+ const onAbort = () => {
440
+ abortedByCaller = true;
441
+ controller.abort();
442
+ };
443
+ if (callerSignal) {
444
+ if (callerSignal.aborted) onAbort();
445
+ else callerSignal.addEventListener("abort", onAbort, { once: true });
446
+ }
447
+ return {
448
+ signal: controller.signal,
449
+ dispose: () => {
450
+ clearTimeout(timer);
451
+ if (callerSignal) callerSignal.removeEventListener("abort", onAbort);
452
+ },
453
+ timedOut: () => timedOut,
454
+ abortedByCaller: () => abortedByCaller
455
+ };
456
+ }
457
+ function shouldRetryStatus(status, policy) {
458
+ if (policy === "idempotent") return status >= 500 || status === 429;
459
+ return status === 429 || status === 503;
460
+ }
461
+ async function requestBuffered(args) {
462
+ const endpoint = endpointLabel(args.method, args.url);
463
+ const doFetch = resolveFetch(args.fetchImpl);
464
+ let lastError = null;
465
+ for (let attempt = 0; attempt <= args.maxRetries; attempt++) {
466
+ const d = createDeadline(args.timeoutMs, args.callerSignal);
467
+ try {
468
+ const res = await doFetch(args.url, {
469
+ method: args.method,
470
+ headers: args.headers,
471
+ body: args.body,
472
+ signal: d.signal,
473
+ redirect: "error",
474
+ // 3xx para outro host vazaria a X-Api-Key
475
+ cache: "no-store"
476
+ // evita cache stale (ex.: polling em Next.js/edge)
477
+ });
478
+ if (shouldRetryStatus(res.status, args.policy) && attempt < args.maxRetries) {
479
+ const wait = retryAfterMs(res.headers) ?? backoffMs(attempt);
480
+ try {
481
+ await res.body?.cancel();
482
+ } catch {
483
+ }
484
+ d.dispose();
485
+ args.onRetry?.({ endpoint, attempt: attempt + 1, delayMs: wait, reason: "status", status: res.status });
486
+ await delay(wait, args.callerSignal);
487
+ continue;
488
+ }
489
+ const text = await res.text();
490
+ d.dispose();
491
+ return { status: res.status, ok: res.ok, text, endpoint, attempts: attempt + 1 };
492
+ } catch (err) {
493
+ d.dispose();
494
+ const meta = { endpoint, attempts: attempt + 1 };
495
+ if (d.timedOut()) throw new ZihinAgentError("timeout", `Request timed out after ${args.timeoutMs}ms.`, meta);
496
+ if (d.abortedByCaller())
497
+ throw new ZihinAgentError("aborted", "Request was aborted by the caller.", meta);
498
+ lastError = errorFromThrow(err, args.timeoutMs, meta);
499
+ const canRetry = args.policy === "idempotent" && lastError.retryable && attempt < args.maxRetries;
500
+ if (!canRetry) throw lastError;
501
+ const wait = backoffMs(attempt);
502
+ args.onRetry?.({ endpoint, attempt: attempt + 1, delayMs: wait, reason: "network" });
503
+ await delay(wait, args.callerSignal);
504
+ }
505
+ }
506
+ throw lastError ?? new ZihinAgentError("unknown", "Max retries exceeded.", { endpoint });
507
+ }
508
+ async function openStream(args) {
509
+ const endpoint = endpointLabel("POST", args.url);
510
+ const doFetch = resolveFetch(args.fetchImpl);
511
+ for (let attempt = 0; attempt <= args.maxRetries; attempt++) {
512
+ const d = createDeadline(args.timeoutMs, args.callerSignal);
513
+ try {
514
+ const response = await doFetch(args.url, {
515
+ method: "POST",
516
+ headers: { ...args.headers, Accept: "text/event-stream" },
517
+ body: args.body,
518
+ signal: d.signal,
519
+ redirect: "error",
520
+ cache: "no-store"
521
+ });
522
+ if (shouldRetryStatus(response.status, "side-effecting") && attempt < args.maxRetries) {
523
+ const wait = retryAfterMs(response.headers) ?? backoffMs(attempt);
524
+ try {
525
+ await response.body?.cancel();
526
+ } catch {
527
+ }
528
+ d.dispose();
529
+ args.onRetry?.({
530
+ endpoint,
531
+ attempt: attempt + 1,
532
+ delayMs: wait,
533
+ reason: "status",
534
+ status: response.status
535
+ });
536
+ await delay(wait, args.callerSignal);
537
+ continue;
538
+ }
539
+ return { response, deadline: d, endpoint };
540
+ } catch (err) {
541
+ d.dispose();
542
+ const meta = { endpoint, attempts: attempt + 1 };
543
+ if (d.timedOut()) throw new ZihinAgentError("timeout", `Request timed out after ${args.timeoutMs}ms.`, meta);
544
+ if (d.abortedByCaller())
545
+ throw new ZihinAgentError("aborted", "Request was aborted by the caller.", meta);
546
+ throw errorFromThrow(err, args.timeoutMs, meta);
547
+ }
548
+ }
549
+ throw new ZihinAgentError("unknown", "Max retries exceeded.", { endpoint });
550
+ }
551
+ function parseBody(text) {
552
+ if (!text) return {};
553
+ try {
554
+ return JSON.parse(text);
555
+ } catch {
556
+ return { message: text };
557
+ }
558
+ }
559
+
560
+ // src/agents.ts
561
+ async function listAgents(cfg, opts = {}) {
562
+ const res = await requestBuffered({
563
+ method: "GET",
564
+ url: `${cfg.baseUrl}/api/v2/agents`,
565
+ headers: buildHeaders(cfg.apiKey),
566
+ timeoutMs: cfg.timeoutMs,
567
+ maxRetries: cfg.maxRetries,
568
+ policy: "idempotent",
569
+ callerSignal: opts.signal,
570
+ fetchImpl: cfg.fetchImpl,
571
+ onRetry: cfg.onRetry
572
+ });
573
+ if (!res.ok) {
574
+ throw errorFromResponse(res.status, parseBody(res.text), { endpoint: res.endpoint, attempts: res.attempts });
575
+ }
576
+ let json;
577
+ try {
578
+ json = JSON.parse(res.text);
579
+ } catch {
580
+ throw new ZihinAgentError("protocol", "Expected JSON from GET /api/v2/agents.");
581
+ }
582
+ const data = rec(json).data;
583
+ const list = Array.isArray(data) ? data : [];
584
+ return list.map((item) => {
585
+ const a = rec(item);
586
+ return {
587
+ id: str(a.id) ?? "",
588
+ name: str(a.name),
589
+ commercialName: str(a.commercial_name),
590
+ bio: str(a.bio),
591
+ type: str(a.type),
592
+ status: str(a.status),
593
+ tags: Array.isArray(a.tags) ? a.tags.filter((t) => typeof t === "string") : void 0,
594
+ toolsCount: typeof a.tools_count === "number" ? a.tools_count : void 0
595
+ };
596
+ });
597
+ }
598
+
599
+ // src/sse.ts
600
+ var DEFAULT_MAX_EVENT_BYTES2 = 1 * 1024 * 1024;
601
+ var DEFAULT_MAX_TOTAL_BYTES2 = 64 * 1024 * 1024;
602
+ async function* parseSse(response, opts = {}) {
603
+ if (!response.body) {
604
+ throw new ZihinAgentError("protocol", "SSE response has no body.");
605
+ }
606
+ const maxEventBytes = opts.maxEventBytes ?? DEFAULT_MAX_EVENT_BYTES2;
607
+ const maxTotalBytes = opts.maxTotalBytes ?? DEFAULT_MAX_TOTAL_BYTES2;
608
+ const reader = response.body.getReader();
609
+ const decoder = new TextDecoder("utf-8");
610
+ let buffer = "";
611
+ let eventName = "";
612
+ let dataLines = [];
613
+ let dataBytes = 0;
614
+ let totalBytes = 0;
615
+ const flush = () => {
616
+ const had = dataLines.length > 0 || eventName !== "";
617
+ const rawData = dataLines.join("\n");
618
+ const evt = eventName || "message";
619
+ eventName = "";
620
+ dataLines = [];
621
+ dataBytes = 0;
622
+ if (!had || rawData === "") return null;
623
+ let data = rawData;
624
+ try {
625
+ data = JSON.parse(rawData);
626
+ } catch {
627
+ }
628
+ return { event: evt, data, rawData };
629
+ };
630
+ try {
631
+ while (true) {
632
+ if (opts.signal?.aborted) {
633
+ throw opts.onAbortError ? opts.onAbortError() : new ZihinAgentError("aborted", "Stream aborted.");
634
+ }
635
+ let chunk;
636
+ try {
637
+ chunk = await reader.read();
638
+ } catch (err) {
639
+ throw opts.onAbortError ? opts.onAbortError() : errorFromThrow(err);
640
+ }
641
+ if (chunk.done) break;
642
+ totalBytes += chunk.value.byteLength;
643
+ if (totalBytes > maxTotalBytes) {
644
+ throw new ZihinAgentError("protocol", `SSE stream exceeded ${maxTotalBytes} bytes.`);
645
+ }
646
+ buffer += decoder.decode(chunk.value, { stream: true });
647
+ let nl;
648
+ while ((nl = buffer.indexOf("\n")) !== -1) {
649
+ let line = buffer.slice(0, nl);
650
+ buffer = buffer.slice(nl + 1);
651
+ if (line.endsWith("\r")) line = line.slice(0, -1);
652
+ if (line === "") {
653
+ const e = flush();
654
+ if (e) yield e;
655
+ continue;
656
+ }
657
+ if (line.startsWith(":")) continue;
658
+ const colon = line.indexOf(":");
659
+ const field = colon === -1 ? line : line.slice(0, colon);
660
+ let val = colon === -1 ? "" : line.slice(colon + 1);
661
+ if (val.startsWith(" ")) val = val.slice(1);
662
+ if (field === "event") {
663
+ eventName = val;
664
+ } else if (field === "data") {
665
+ dataBytes += utf8Len(val) + 1;
666
+ if (dataBytes > maxEventBytes) {
667
+ throw new ZihinAgentError("protocol", `SSE event exceeded ${maxEventBytes} bytes.`);
668
+ }
669
+ dataLines.push(val);
670
+ }
671
+ }
672
+ if (utf8Len(buffer) > maxEventBytes) {
673
+ throw new ZihinAgentError("protocol", `SSE line exceeded ${maxEventBytes} bytes.`);
674
+ }
675
+ }
676
+ buffer += decoder.decode();
677
+ if (buffer.length > 0) {
678
+ let line = buffer.endsWith("\r") ? buffer.slice(0, -1) : buffer;
679
+ if (line && !line.startsWith(":")) {
680
+ const colon = line.indexOf(":");
681
+ const field = colon === -1 ? line : line.slice(0, colon);
682
+ let val = colon === -1 ? "" : line.slice(colon + 1);
683
+ if (val.startsWith(" ")) val = val.slice(1);
684
+ if (field === "data") dataLines.push(val);
685
+ else if (field === "event") eventName = val;
686
+ }
687
+ }
688
+ const tail = flush();
689
+ if (tail) yield tail;
690
+ } finally {
691
+ try {
692
+ await reader.cancel();
693
+ } catch {
694
+ }
695
+ opts.onEnd?.();
696
+ }
697
+ }
698
+
699
+ // src/invoke.ts
700
+ function mapEvent(e) {
701
+ const d = rec(e.data);
702
+ switch (e.event) {
703
+ case "metadata":
704
+ return {
705
+ type: "metadata",
706
+ requestId: str(d.request_id),
707
+ agentId: str(d.agent_id),
708
+ sessionId: str(d.session_id),
709
+ data: e.data
710
+ };
711
+ case "status":
712
+ return { type: "status", phase: str(d.phase), data: e.data };
713
+ case "attachments":
714
+ return { type: "attachments", data: e.data };
715
+ case "phase":
716
+ return { type: "phase", phase: str(d.phase), data: e.data };
717
+ case "thinking":
718
+ return { type: "thinking", thought: str(d.thought), data: e.data };
719
+ case "tool":
720
+ return {
721
+ type: "tool",
722
+ tool: str(d.tool),
723
+ callId: str(d.call_id),
724
+ success: typeof d.success === "boolean" ? d.success : void 0,
725
+ data: e.data
726
+ };
727
+ case "action_proposed":
728
+ return { type: "action_proposed", data: e.data };
729
+ case "token":
730
+ return { type: "token", content: str(d.content) ?? "", data: e.data };
731
+ case "warning":
732
+ return { type: "warning", message: str(d.message), data: e.data };
733
+ case "response":
734
+ return {
735
+ type: "response",
736
+ content: str(d.content) ?? "",
737
+ sessionId: str(d.session_id),
738
+ sources: Array.isArray(d.sources) ? d.sources : void 0,
739
+ blocks: Array.isArray(d.blocks) ? d.blocks : void 0,
740
+ data: e.data
741
+ };
742
+ case "metrics": {
743
+ const modelConfig = rec(d.model_config);
744
+ return {
745
+ type: "metrics",
746
+ usage: parseUsage(d.usage),
747
+ model: str(modelConfig.model) ?? str(d.model),
748
+ iterations: num(d.iterations),
749
+ executionTimeMs: num(d.execution_time_ms),
750
+ data: e.data
751
+ };
752
+ }
753
+ default:
754
+ return { type: "unknown", event: e.event, data: e.data };
755
+ }
756
+ }
757
+ function buildRequestBody(params) {
758
+ const body = {};
759
+ if (params.message !== void 0) body.message = params.message;
760
+ if (params.sessionId) body.session_id = params.sessionId;
761
+ if (params.attachments?.length) {
762
+ body.attachments = params.attachments.map((a) => ({ url: a.url, type: a.type }));
763
+ }
764
+ if (params.options) {
765
+ const o = {};
766
+ if (params.options.temperature !== void 0) o.temperature = params.options.temperature;
767
+ if (params.options.maxTokens !== void 0) o.max_tokens = params.options.maxTokens;
768
+ if (params.options.timeoutMs !== void 0) o.timeout_ms = params.options.timeoutMs;
769
+ if (params.options.jitMode !== void 0) o.jit_mode = params.options.jitMode;
770
+ if (Object.keys(o).length) body.options = o;
771
+ }
772
+ if (params.metadata) {
773
+ const m = { ...params.metadata };
774
+ if (params.metadata.traceId !== void 0) {
775
+ m.trace_id = params.metadata.traceId;
776
+ delete m.traceId;
777
+ }
778
+ if (params.metadata.correlationId !== void 0) {
779
+ m.correlation_id = params.metadata.correlationId;
780
+ delete m.correlationId;
781
+ }
782
+ body.metadata = m;
783
+ }
784
+ return body;
785
+ }
786
+ function validateParams(params) {
787
+ if (!params.agentId) {
788
+ throw new ZihinAgentError("config", "agentId is required.");
789
+ }
790
+ if (!params.message && !(params.attachments && params.attachments.length)) {
791
+ throw new ZihinAgentError("config", "Either `message` or `attachments` is required.");
792
+ }
793
+ if (params.message) {
794
+ const sizeCheck = validatePromptSize(params.message);
795
+ if (!sizeCheck.valid) {
796
+ throw new ZihinAgentError("config", sizeCheck.error || "Message too large.");
797
+ }
798
+ }
799
+ for (const att of params.attachments ?? []) {
800
+ const urlCheck = validateBaseUrl(att.url, { allowCustomDomains: true });
801
+ if (!urlCheck.valid) {
802
+ throw new ZihinAgentError("config", `Invalid attachment URL: ${urlCheck.error}`);
803
+ }
804
+ }
805
+ }
806
+ async function* streamAgent(cfg, params) {
807
+ validateParams(params);
808
+ const timeoutMs = params.options?.timeoutMs ?? cfg.timeoutMs;
809
+ const url = `${cfg.baseUrl}/api/v2/agents/${encodeURIComponent(params.agentId)}/stream`;
810
+ const { response, deadline, endpoint } = await openStream({
811
+ url,
812
+ headers: buildHeaders(cfg.apiKey),
813
+ body: JSON.stringify(buildRequestBody(params)),
814
+ timeoutMs,
815
+ maxRetries: cfg.maxRetries,
816
+ callerSignal: params.signal,
817
+ fetchImpl: cfg.fetchImpl,
818
+ onRetry: cfg.onRetry
819
+ });
820
+ try {
821
+ if (!response.ok) {
822
+ let text = "";
823
+ try {
824
+ text = await response.text();
825
+ } catch {
826
+ }
827
+ throw errorFromResponse(response.status, parseBody(text), { endpoint });
828
+ }
829
+ const onAbortError = () => deadline.timedOut() ? new ZihinAgentError("timeout", `Stream timed out after ${timeoutMs}ms.`, { endpoint }) : deadline.abortedByCaller() ? new ZihinAgentError("aborted", "Stream aborted by the caller.", { endpoint }) : new ZihinAgentError("network", "Stream connection interrupted.", { endpoint });
830
+ for await (const e of parseSse(response, {
831
+ signal: params.signal,
832
+ onAbortError,
833
+ maxEventBytes: cfg.maxStreamEventBytes,
834
+ maxTotalBytes: cfg.maxStreamTotalBytes
835
+ })) {
836
+ yield mapEvent(e);
837
+ }
838
+ } finally {
839
+ deadline.dispose();
840
+ }
841
+ }
842
+ async function invokeAgent(cfg, params) {
843
+ const raw = [];
844
+ const keepRaw = params.includeRaw === true;
845
+ let responseChunk;
846
+ let metricsChunk;
847
+ let metadataSessionId;
848
+ let tokenBuffer = "";
849
+ for await (const chunk of streamAgent(cfg, params)) {
850
+ if (keepRaw) raw.push(chunk);
851
+ if (chunk.type === "response") responseChunk = chunk;
852
+ else if (chunk.type === "metrics") metricsChunk = chunk;
853
+ else if (chunk.type === "token") tokenBuffer += chunk.content;
854
+ else if (chunk.type === "metadata" && chunk.sessionId) metadataSessionId = chunk.sessionId;
855
+ }
856
+ if (!responseChunk) {
857
+ if (tokenBuffer) {
858
+ return {
859
+ content: tokenBuffer,
860
+ sessionId: metadataSessionId ?? params.sessionId,
861
+ usage: metricsChunk?.usage,
862
+ model: metricsChunk?.model,
863
+ iterations: metricsChunk?.iterations,
864
+ executionTimeMs: metricsChunk?.executionTimeMs,
865
+ raw
866
+ };
867
+ }
868
+ throw new ZihinAgentError("protocol", "Stream ended without a `response` event.");
869
+ }
870
+ return {
871
+ content: responseChunk.content || tokenBuffer,
872
+ sessionId: responseChunk.sessionId ?? metadataSessionId ?? params.sessionId,
873
+ model: metricsChunk?.model,
874
+ usage: metricsChunk?.usage,
875
+ sources: responseChunk.sources,
876
+ blocks: responseChunk.blocks,
877
+ iterations: metricsChunk?.iterations,
878
+ executionTimeMs: metricsChunk?.executionTimeMs,
879
+ raw
880
+ };
881
+ }
882
+
883
+ // src/triggers.ts
884
+ async function requestJson(cfg, args) {
885
+ const res = await requestBuffered({
886
+ method: args.method,
887
+ url: `${cfg.baseUrl}${args.path}`,
888
+ headers: buildHeaders(cfg.apiKey),
889
+ body: args.body !== void 0 ? JSON.stringify(args.body) : void 0,
890
+ timeoutMs: cfg.timeoutMs,
891
+ maxRetries: cfg.maxRetries,
892
+ policy: args.policy,
893
+ callerSignal: args.signal,
894
+ fetchImpl: cfg.fetchImpl,
895
+ onRetry: cfg.onRetry
896
+ });
897
+ if (!res.ok) {
898
+ throw errorFromResponse(res.status, parseBody(res.text), { endpoint: res.endpoint, attempts: res.attempts });
899
+ }
900
+ try {
901
+ return JSON.parse(res.text);
902
+ } catch {
903
+ throw new ZihinAgentError("protocol", "Expected JSON response.", { endpoint: res.endpoint });
904
+ }
905
+ }
906
+ function queryExtractionBody(extraction, queryField) {
907
+ if (!extraction) return { mode: "field", field: queryField ?? "message" };
908
+ switch (extraction.mode) {
909
+ case "field":
910
+ if (!extraction.field) {
911
+ throw new ZihinAgentError("config", 'queryExtraction.field is required for mode "field".');
912
+ }
913
+ return { mode: "field", field: extraction.field };
914
+ case "template":
915
+ if (!extraction.template) {
916
+ throw new ZihinAgentError("config", 'queryExtraction.template is required for mode "template".');
917
+ }
918
+ return { mode: "template", template: extraction.template };
919
+ case "full_body":
920
+ return { mode: "full_body" };
921
+ default:
922
+ throw new ZihinAgentError("config", "Unknown queryExtraction.mode (use field|template|full_body).");
923
+ }
924
+ }
925
+ async function createTrigger(cfg, params) {
926
+ if (!params.agentId) throw new ZihinAgentError("config", "agentId is required.");
927
+ if (!params.name) throw new ZihinAgentError("config", "name is required.");
928
+ const mode = params.mode ?? "async";
929
+ if (mode === "async" && !params.callbackUrl) {
930
+ throw new ZihinAgentError("config", "callbackUrl is required for async triggers.");
931
+ }
932
+ if (params.callbackUrl) {
933
+ const cb = validateBaseUrl(params.callbackUrl, { allowCustomDomains: true });
934
+ if (!cb.valid) {
935
+ throw new ZihinAgentError("config", `Invalid callbackUrl: ${cb.error}`);
936
+ }
937
+ }
938
+ const execution = { mode };
939
+ if (params.callbackUrl) {
940
+ execution.callback = {
941
+ url: params.callbackUrl,
942
+ method: params.callbackMethod ?? "POST"
943
+ };
944
+ }
945
+ const body = {
946
+ agent_id: params.agentId,
947
+ name: params.name,
948
+ trigger_type: "webhook",
949
+ trigger_config: {
950
+ query_extraction: queryExtractionBody(params.queryExtraction, params.queryField),
951
+ auth: { type: params.dispatchAuth ?? "api_key" },
952
+ execution
953
+ }
954
+ };
955
+ const json = rec(
956
+ await requestJson(cfg, { method: "POST", path: "/api/triggers", body, policy: "side-effecting", signal: params.signal })
957
+ );
958
+ const data = rec(json.data);
959
+ const triggerId = str(data.id) ?? str(json.id);
960
+ const webhookUrl = str(json.webhook_url) ?? (triggerId ? `${cfg.baseUrl}/api/triggers/webhook/${triggerId}` : void 0);
961
+ if (!triggerId || !webhookUrl) {
962
+ throw new ZihinAgentError("protocol", "createTrigger: missing trigger id / webhook url in response.");
963
+ }
964
+ return { triggerId, webhookUrl };
965
+ }
966
+ async function dispatchAgent(cfg, params) {
967
+ if (!params.triggerId) throw new ZihinAgentError("config", "triggerId is required.");
968
+ const res = await requestBuffered({
969
+ method: "POST",
970
+ url: `${cfg.baseUrl}/api/triggers/webhook/${encodeURIComponent(params.triggerId)}`,
971
+ headers: buildHeaders(cfg.apiKey),
972
+ body: JSON.stringify(params.payload ?? {}),
973
+ timeoutMs: cfg.timeoutMs,
974
+ maxRetries: cfg.maxRetries,
975
+ policy: "side-effecting",
976
+ // S1: nunca duplica disparo por erro de rede
977
+ callerSignal: params.signal,
978
+ fetchImpl: cfg.fetchImpl,
979
+ onRetry: cfg.onRetry
980
+ });
981
+ if (!res.ok) {
982
+ throw errorFromResponse(res.status, parseBody(res.text), { endpoint: res.endpoint, attempts: res.attempts });
983
+ }
984
+ const json = parseBody(res.text);
985
+ const j = rec(json);
986
+ return {
987
+ accepted: true,
988
+ triggerId: str(j.trigger_id) ?? params.triggerId,
989
+ executionId: str(j.execution_id),
990
+ agentResponse: str(j.agent_response),
991
+ usage: parseUsage(j.usage),
992
+ raw: json
993
+ };
994
+ }
995
+ function webhookUrlFor(cfg, t) {
996
+ if (str(t.webhook_url)) return str(t.webhook_url);
997
+ const id = str(t.id);
998
+ return t.trigger_type === "webhook" && id ? `${cfg.baseUrl}/api/triggers/webhook/${id}` : void 0;
999
+ }
1000
+ function mapTriggerInfo(cfg, raw) {
1001
+ const t = rec(raw);
1002
+ return {
1003
+ id: str(t.id) ?? "",
1004
+ agentId: str(t.agent_id),
1005
+ name: str(t.name),
1006
+ description: str(t.description),
1007
+ triggerType: str(t.trigger_type),
1008
+ enabled: typeof t.enabled === "boolean" ? t.enabled : void 0,
1009
+ triggerCount: num(t.trigger_count),
1010
+ lastError: str(t.last_error),
1011
+ webhookUrl: webhookUrlFor(cfg, t)
1012
+ };
1013
+ }
1014
+ async function listTriggers(cfg, params = {}) {
1015
+ const qs = new URLSearchParams();
1016
+ if (params.agentId) qs.set("agentId", params.agentId);
1017
+ if (params.triggerType) qs.set("triggerType", params.triggerType);
1018
+ if (params.enabledOnly) qs.set("enabledOnly", "true");
1019
+ const query = qs.toString();
1020
+ const path = `/api/triggers${query ? `?${query}` : ""}`;
1021
+ const json = await requestJson(cfg, { method: "GET", path, policy: "idempotent", signal: params.signal });
1022
+ const list = Array.isArray(json) ? json : Array.isArray(rec(json).data) ? rec(json).data : [];
1023
+ return list.map((t) => mapTriggerInfo(cfg, t));
1024
+ }
1025
+ async function getTrigger(cfg, params) {
1026
+ if (!params.triggerId) throw new ZihinAgentError("config", "triggerId is required.");
1027
+ const json = await requestJson(cfg, {
1028
+ method: "GET",
1029
+ path: `/api/triggers/${encodeURIComponent(params.triggerId)}`,
1030
+ policy: "idempotent",
1031
+ signal: params.signal
1032
+ });
1033
+ return mapTriggerInfo(cfg, rec(json).data ?? json);
1034
+ }
1035
+ async function updateTrigger(cfg, params) {
1036
+ if (!params.triggerId) throw new ZihinAgentError("config", "triggerId is required.");
1037
+ if (params.callbackUrl) {
1038
+ const cb = validateBaseUrl(params.callbackUrl, { allowCustomDomains: true });
1039
+ if (!cb.valid) {
1040
+ throw new ZihinAgentError("config", `Invalid callbackUrl: ${cb.error}`);
1041
+ }
1042
+ }
1043
+ const body = {};
1044
+ if (params.name !== void 0) body.name = params.name;
1045
+ if (params.description !== void 0) body.description = params.description;
1046
+ if (params.enabled !== void 0) body.enabled = params.enabled;
1047
+ const touchesConfig = params.queryExtraction !== void 0 || params.callbackUrl !== void 0 || params.callbackMethod !== void 0 || params.mode !== void 0;
1048
+ if (touchesConfig) {
1049
+ const path = `/api/triggers/${encodeURIComponent(params.triggerId)}`;
1050
+ const currentJson = rec(
1051
+ await requestJson(cfg, { method: "GET", path, policy: "idempotent", signal: params.signal })
1052
+ );
1053
+ const current = rec(currentJson.data ?? currentJson);
1054
+ if (!current.trigger_config || typeof current.trigger_config !== "object" || Object.keys(rec(current.trigger_config)).length === 0) {
1055
+ throw new ZihinAgentError(
1056
+ "protocol",
1057
+ "updateTrigger: current trigger_config unavailable \u2014 refusing to overwrite it partially."
1058
+ );
1059
+ }
1060
+ const config = rec(current.trigger_config);
1061
+ const merged = { ...config };
1062
+ if (params.queryExtraction !== void 0) {
1063
+ merged.query_extraction = queryExtractionBody(params.queryExtraction, void 0);
1064
+ }
1065
+ if (params.mode !== void 0 || params.callbackUrl !== void 0 || params.callbackMethod !== void 0) {
1066
+ const execution = rec(config.execution);
1067
+ const callback = rec(execution.callback);
1068
+ const newExecution = { ...execution };
1069
+ if (params.mode !== void 0) newExecution.mode = params.mode;
1070
+ if (params.callbackUrl !== void 0 || params.callbackMethod !== void 0) {
1071
+ const url = params.callbackUrl ?? str(callback.url);
1072
+ if (!url) {
1073
+ throw new ZihinAgentError(
1074
+ "config",
1075
+ "callbackUrl is required: this trigger has no callback configured yet."
1076
+ );
1077
+ }
1078
+ newExecution.callback = {
1079
+ url,
1080
+ method: params.callbackMethod ?? str(callback.method) ?? "POST"
1081
+ };
1082
+ }
1083
+ if (newExecution.mode === "async" && !rec(newExecution.callback).url) {
1084
+ throw new ZihinAgentError("config", "callbackUrl is required for async triggers.");
1085
+ }
1086
+ merged.execution = newExecution;
1087
+ }
1088
+ body.trigger_config = merged;
1089
+ }
1090
+ if (Object.keys(body).length === 0) {
1091
+ throw new ZihinAgentError("config", "updateTrigger: no fields to update.");
1092
+ }
1093
+ const json = await requestJson(cfg, {
1094
+ method: "PATCH",
1095
+ path: `/api/triggers/${encodeURIComponent(params.triggerId)}`,
1096
+ body,
1097
+ // PATCH aqui é replay-safe: reaplicar os mesmos valores produz o mesmo estado.
1098
+ policy: "idempotent",
1099
+ signal: params.signal
1100
+ });
1101
+ return mapTriggerInfo(cfg, rec(json).data ?? json);
1102
+ }
1103
+ async function deleteTrigger(cfg, params) {
1104
+ if (!params.triggerId) throw new ZihinAgentError("config", "triggerId is required.");
1105
+ const res = await requestBuffered({
1106
+ method: "DELETE",
1107
+ url: `${cfg.baseUrl}/api/triggers/${encodeURIComponent(params.triggerId)}`,
1108
+ headers: buildHeaders(cfg.apiKey),
1109
+ timeoutMs: cfg.timeoutMs,
1110
+ maxRetries: cfg.maxRetries,
1111
+ policy: "idempotent",
1112
+ // DELETE é idempotente
1113
+ callerSignal: params.signal,
1114
+ fetchImpl: cfg.fetchImpl,
1115
+ onRetry: cfg.onRetry
1116
+ });
1117
+ if (!res.ok) {
1118
+ throw errorFromResponse(res.status, parseBody(res.text), { endpoint: res.endpoint, attempts: res.attempts });
1119
+ }
1120
+ const j = rec(parseBody(res.text));
1121
+ return { deleted: j.deleted === true || res.status === 200 };
1122
+ }
1123
+ async function findOrCreateTrigger(cfg, params) {
1124
+ if (!params.agentId) throw new ZihinAgentError("config", "agentId is required.");
1125
+ if (!params.name) throw new ZihinAgentError("config", "name is required.");
1126
+ const existing = await listTriggers(cfg, { agentId: params.agentId, signal: params.signal });
1127
+ const match = existing.find((t) => t.name === params.name);
1128
+ if (match && match.id) {
1129
+ const webhookUrl = match.webhookUrl ?? `${cfg.baseUrl}/api/triggers/webhook/${match.id}`;
1130
+ return { triggerId: match.id, webhookUrl, created: false };
1131
+ }
1132
+ const ref = await createTrigger(cfg, params);
1133
+ return { ...ref, created: true };
1134
+ }
1135
+ async function getExecution(cfg, params) {
1136
+ if (!params.triggerId || !params.executionId) {
1137
+ throw new ZihinAgentError("config", "triggerId and executionId are required.");
1138
+ }
1139
+ const path = `/api/triggers/${encodeURIComponent(params.triggerId)}/executions/${encodeURIComponent(
1140
+ params.executionId
1141
+ )}`;
1142
+ const json = rec(await requestJson(cfg, { method: "GET", path, policy: "idempotent", signal: params.signal }));
1143
+ const data = rec(json.data);
1144
+ const exec = rec(data.execution ?? json.execution);
1145
+ const metrics = rec(data.metrics ?? json.metrics);
1146
+ return {
1147
+ executionId: str(exec.id) ?? params.executionId,
1148
+ status: str(exec.status) ?? "unknown",
1149
+ startedAt: str(exec.started_at),
1150
+ finishedAt: str(exec.finished_at),
1151
+ executionTimeMs: num(exec.execution_time_ms),
1152
+ usage: parseUsage(metrics.usage ?? metrics) ?? parseUsage(exec.usage),
1153
+ result: str(exec.agent_response) ?? str(exec.result),
1154
+ raw: json
1155
+ };
1156
+ }
1157
+
1158
+ // src/client.ts
1159
+ var ZihinAgentClient = class {
1160
+ constructor(config) {
1161
+ this.cfg = resolveConfig(config);
1162
+ }
1163
+ /** Base URL efetiva (após validação SSRF). */
1164
+ get baseUrl() {
1165
+ return this.cfg.baseUrl;
1166
+ }
1167
+ /** Notifica `config.onError` (falha definitiva); o callback nunca derruba o fluxo. */
1168
+ report(err) {
1169
+ if (this.cfg.onError && err instanceof ZihinAgentError) {
1170
+ try {
1171
+ this.cfg.onError(err);
1172
+ } catch {
1173
+ }
1174
+ }
1175
+ }
1176
+ guard(p) {
1177
+ return p.catch((err) => {
1178
+ this.report(err);
1179
+ throw err;
1180
+ });
1181
+ }
1182
+ // ---- Caminho A: direto (SSE) ----
1183
+ /** Invoca o agente e retorna o resultado final consolidado (response + metrics). */
1184
+ invokeAgent(params) {
1185
+ return this.guard(invokeAgent(this.cfg, params));
1186
+ }
1187
+ /** Emite deltas ao vivo do agente (async iterator). */
1188
+ async *streamAgent(params) {
1189
+ try {
1190
+ yield* streamAgent(this.cfg, params);
1191
+ } catch (err) {
1192
+ this.report(err);
1193
+ throw err;
1194
+ }
1195
+ }
1196
+ // ---- Caminho B: triggers (async/callback) ----
1197
+ /** Provisiona (1x) um trigger de webhook ligado a um agente. */
1198
+ createTrigger(params) {
1199
+ return this.guard(createTrigger(this.cfg, params));
1200
+ }
1201
+ /** Reaproveita (por agente+nome) ou cria um trigger — evita duplicação. */
1202
+ findOrCreateTrigger(params) {
1203
+ return this.guard(findOrCreateTrigger(this.cfg, params));
1204
+ }
1205
+ /** Lista os triggers do tenant (filtros opcionais). */
1206
+ listTriggers(params = {}) {
1207
+ return this.guard(listTriggers(this.cfg, params));
1208
+ }
1209
+ /** Obtém um trigger pelo id (inclui webhookUrl). */
1210
+ getTrigger(params) {
1211
+ return this.guard(getTrigger(this.cfg, params));
1212
+ }
1213
+ /** Atualiza um trigger (name/description/enabled/extração/callback) via PATCH. */
1214
+ updateTrigger(params) {
1215
+ return this.guard(updateTrigger(this.cfg, params));
1216
+ }
1217
+ /** Remove um trigger (requer key admin/owner). */
1218
+ deleteTrigger(params) {
1219
+ return this.guard(deleteTrigger(this.cfg, params));
1220
+ }
1221
+ /** Dispara um trigger: 202 (async) ou JSON imediato (sync). */
1222
+ dispatchAgent(params) {
1223
+ return this.guard(dispatchAgent(this.cfg, params));
1224
+ }
1225
+ /** Consulta o resultado de uma execução disparada por trigger (polling). */
1226
+ getExecution(params) {
1227
+ return this.guard(getExecution(this.cfg, params));
1228
+ }
1229
+ // ---- Auxiliar ----
1230
+ /** Lista os agentes do tenant (dropdown). */
1231
+ listAgents(opts = {}) {
1232
+ return this.guard(listAgents(this.cfg, opts));
1233
+ }
1234
+ };
1235
+ function createClient(config) {
1236
+ return new ZihinAgentClient(config);
1237
+ }
1238
+
1239
+ // src/signing.ts
1240
+ function loadNodeCrypto() {
1241
+ const proc = globalThis.process;
1242
+ if (typeof proc?.getBuiltinModule === "function") {
1243
+ return proc.getBuiltinModule("node:crypto");
1244
+ }
1245
+ try {
1246
+ return __require("crypto");
1247
+ } catch {
1248
+ throw new ZihinAgentError(
1249
+ "config",
1250
+ "signRequest requires Node.js crypto, unavailable in this runtime (the ESM build needs Node >=20.16)."
1251
+ );
1252
+ }
1253
+ }
1254
+ function signRequest(payload, secretKey, timestamp) {
1255
+ const { createHmac, randomBytes } = loadNodeCrypto();
1256
+ const ts = timestamp || Date.now();
1257
+ const nonce = randomBytes(16).toString("hex");
1258
+ const message = `${ts}.${nonce}.${payload}`;
1259
+ const signature = createHmac("sha256", secretKey).update(message).digest("hex");
1260
+ return {
1261
+ "X-Zihin-Signature": `sha256=${signature}`,
1262
+ "X-Zihin-Timestamp": ts.toString(),
1263
+ "X-Zihin-Nonce": nonce
1264
+ };
1265
+ }
1266
+ export {
1267
+ DEFAULT_LIMITS,
1268
+ ZihinAgentClient,
1269
+ ZihinAgentError,
1270
+ createClient,
1271
+ sanitizeApiError,
1272
+ signRequest,
1273
+ validateApiKeyFormat,
1274
+ validateBaseUrl,
1275
+ validatePromptSize
1276
+ };