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