@zihin/agent-client 0.1.0 → 0.4.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,1716 @@
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
+ DEADLINE_GRACE_MS: () => DEADLINE_GRACE_MS,
24
+ DEFAULT_LIMITS: () => DEFAULT_LIMITS,
25
+ MAX_TASK_PAYLOAD_BYTES: () => MAX_TASK_PAYLOAD_BYTES,
26
+ OPERATION_TIMEOUT_MS: () => OPERATION_TIMEOUT_MS,
27
+ ZihinAgentClient: () => ZihinAgentClient,
28
+ ZihinAgentError: () => ZihinAgentError,
29
+ createClient: () => createClient,
30
+ isCommercialBlock: () => isCommercialBlock,
31
+ sanitizeApiError: () => sanitizeApiError,
32
+ signRequest: () => signRequest,
33
+ validateApiKeyFormat: () => validateApiKeyFormat,
34
+ validateBaseUrl: () => validateBaseUrl,
35
+ validatePromptSize: () => validatePromptSize
36
+ });
37
+ module.exports = __toCommonJS(index_exports);
38
+
39
+ // src/internal.ts
40
+ var _utf8 = new TextEncoder();
41
+ function utf8Len(s) {
42
+ return _utf8.encode(s).length;
43
+ }
44
+ function rec(x) {
45
+ return x && typeof x === "object" ? x : {};
46
+ }
47
+ function str(x) {
48
+ return typeof x === "string" ? x : void 0;
49
+ }
50
+ function num(x) {
51
+ return typeof x === "number" ? x : void 0;
52
+ }
53
+ function parseUsage(usageRaw) {
54
+ const u = rec(usageRaw);
55
+ if (Object.keys(u).length === 0) return void 0;
56
+ return {
57
+ inputTokens: num(u.input_tokens) ?? num(u.prompt_tokens),
58
+ outputTokens: num(u.output_tokens) ?? num(u.completion_tokens),
59
+ totalTokens: num(u.total_tokens),
60
+ costUsd: num(u.cost_usd)
61
+ };
62
+ }
63
+ var RUNNING_STATUSES = /* @__PURE__ */ new Set([
64
+ "running",
65
+ "pending",
66
+ "queued",
67
+ "processing",
68
+ "in_progress",
69
+ "waiting",
70
+ "retrying",
71
+ "scheduled",
72
+ "awaiting_approval"
73
+ // HITL: turno pausado esperando decisão humana não terminou
74
+ ]);
75
+ function isTerminalStatus(status) {
76
+ return !RUNNING_STATUSES.has(status.toLowerCase().replace(/-/g, "_"));
77
+ }
78
+
79
+ // src/security.ts
80
+ var PRIVATE_IP_PATTERNS = [
81
+ /^127\./,
82
+ // 127.0.0.0/8 (localhost)
83
+ /^10\./,
84
+ // 10.0.0.0/8 (private)
85
+ /^172\.(1[6-9]|2[0-9]|3[0-1])\./,
86
+ // 172.16.0.0/12 (private)
87
+ /^192\.168\./,
88
+ // 192.168.0.0/16 (private)
89
+ /^169\.254\./,
90
+ // 169.254.0.0/16 (link-local)
91
+ /^0\./
92
+ // 0.0.0.0/8
93
+ ];
94
+ function parseIpv6(host) {
95
+ if (!host.includes(":")) return null;
96
+ let text = host;
97
+ const words = [];
98
+ const v4 = /(\d{1,3}(?:\.\d{1,3}){3})$/.exec(text);
99
+ let tail = [];
100
+ if (v4) {
101
+ const octets = v4[1].split(".").map(Number);
102
+ if (octets.some((o) => !Number.isInteger(o) || o < 0 || o > 255)) return null;
103
+ tail = [octets[0] << 8 | octets[1], octets[2] << 8 | octets[3]];
104
+ text = text.slice(0, v4.index).replace(/:$/, "") || "::";
105
+ }
106
+ const halves = text.split("::");
107
+ if (halves.length > 2) return null;
108
+ const toWords = (part) => {
109
+ if (!part) return [];
110
+ const out = [];
111
+ for (const group of part.split(":")) {
112
+ if (!/^[0-9a-f]{1,4}$/i.test(group)) return null;
113
+ out.push(parseInt(group, 16));
114
+ }
115
+ return out;
116
+ };
117
+ const head = toWords(halves[0]);
118
+ const rest = halves.length === 2 ? toWords(halves[1]) : [];
119
+ if (!head || !rest) return null;
120
+ if (halves.length === 2) {
121
+ const fill = 8 - (head.length + rest.length + tail.length);
122
+ if (fill < 0) return null;
123
+ words.push(...head, ...Array(fill).fill(0), ...rest, ...tail);
124
+ } else {
125
+ words.push(...head, ...tail);
126
+ }
127
+ return words.length === 8 ? words : null;
128
+ }
129
+ function isPrivateIpv6(words) {
130
+ const [w0, w1, w2, w3, w4, w5] = words;
131
+ const allZero = words.every((w) => w === 0);
132
+ if (allZero) return true;
133
+ if (words.slice(0, 7).every((w) => w === 0) && words[7] === 1) return true;
134
+ if (w0 === 0 && w1 === 0 && w2 === 0 && w3 === 0 && w4 === 0 && (w5 === 0 || w5 === 65535)) {
135
+ const v4 = `${words[6] >> 8}.${words[6] & 255}.${words[7] >> 8}.${words[7] & 255}`;
136
+ return PRIVATE_IP_PATTERNS.some((p) => p.test(v4));
137
+ }
138
+ if ((w0 >> 8 & 254) === 252) return true;
139
+ if ((w0 & 65472) === 65152) return true;
140
+ if ((w0 & 65472) === 65216) return true;
141
+ return false;
142
+ }
143
+ function bareHostname(hostname) {
144
+ const bare = hostname.startsWith("[") && hostname.endsWith("]") ? hostname.slice(1, -1) : hostname;
145
+ return bare.length > 1 && bare.endsWith(".") ? bare.replace(/\.+$/, "") : bare;
146
+ }
147
+ var BLOCKED_HOSTNAMES = [
148
+ "localhost",
149
+ "localtest.me",
150
+ "metadata.google.internal",
151
+ // GCP metadata
152
+ "169.254.169.254",
153
+ // AWS/Azure/GCP metadata
154
+ "metadata.azure.com"
155
+ ];
156
+ var ALLOWED_ZIHIN_DOMAINS = [
157
+ "llm.zihin.ai",
158
+ "api.zihin.ai",
159
+ "staging.llm.zihin.ai",
160
+ "dev.llm.zihin.ai"
161
+ ];
162
+ function validateBaseUrl(url, options = {}) {
163
+ if (!url || url.trim() === "") {
164
+ return { valid: true, sanitizedUrl: "https://llm.zihin.ai" };
165
+ }
166
+ try {
167
+ const parsed = new URL(url);
168
+ if (parsed.hostname.length > 1 && parsed.hostname.endsWith(".")) {
169
+ parsed.hostname = parsed.hostname.replace(/\.+$/, "");
170
+ }
171
+ const hostname = bareHostname(parsed.hostname);
172
+ const isLocalhost = hostname === "localhost" || hostname === "127.0.0.1" || hostname === "::1";
173
+ if (isLocalhost && options.allowLocalhost) {
174
+ const sanitizedUrl2 = parsed.origin + parsed.pathname.replace(/\/+$/, "");
175
+ return { valid: true, sanitizedUrl: sanitizedUrl2 };
176
+ }
177
+ if (!options.allowHttp && parsed.protocol !== "https:") {
178
+ return {
179
+ valid: false,
180
+ error: "Only HTTPS URLs are allowed for security reasons"
181
+ };
182
+ }
183
+ const ipv6 = parseIpv6(hostname);
184
+ if (ipv6) {
185
+ if (isPrivateIpv6(ipv6)) {
186
+ return { valid: false, error: "Private IP addresses are not allowed" };
187
+ }
188
+ } else {
189
+ for (const pattern of PRIVATE_IP_PATTERNS) {
190
+ if (pattern.test(hostname)) {
191
+ return { valid: false, error: "Private IP addresses are not allowed" };
192
+ }
193
+ }
194
+ }
195
+ for (const blocked of BLOCKED_HOSTNAMES) {
196
+ if (blocked.startsWith("*")) {
197
+ const suffix = blocked.slice(1);
198
+ if (hostname.endsWith(suffix)) {
199
+ return { valid: false, error: "This hostname is not allowed" };
200
+ }
201
+ } else if (hostname === blocked || hostname.endsWith("." + blocked)) {
202
+ return { valid: false, error: "This hostname is not allowed" };
203
+ }
204
+ }
205
+ if (!options.allowCustomDomains) {
206
+ const isAllowed = ALLOWED_ZIHIN_DOMAINS.some(
207
+ (domain) => hostname === domain || hostname.endsWith("." + domain)
208
+ );
209
+ if (!isAllowed) {
210
+ return {
211
+ valid: false,
212
+ error: `Only Zihin domains are allowed. Use 'allowCustomBaseUrl' for custom endpoints.`
213
+ };
214
+ }
215
+ }
216
+ const sanitizedUrl = parsed.origin + parsed.pathname.replace(/\/+$/, "");
217
+ return { valid: true, sanitizedUrl };
218
+ } catch {
219
+ return { valid: false, error: "Invalid URL format" };
220
+ }
221
+ }
222
+ var DEFAULT_LIMITS = {
223
+ maxPromptBytes: 100 * 1024,
224
+ // 100KB
225
+ maxMessageCount: 100,
226
+ maxSingleMessageBytes: 50 * 1024,
227
+ // 50KB
228
+ maxTotalPayloadBytes: 500 * 1024
229
+ // 500KB
230
+ };
231
+ function validatePromptSize(prompt, limits = {}) {
232
+ const effectiveLimits = { ...DEFAULT_LIMITS, ...limits };
233
+ const bytes = utf8Len(prompt);
234
+ if (bytes > effectiveLimits.maxPromptBytes) {
235
+ return {
236
+ valid: false,
237
+ error: `Prompt size (${Math.round(bytes / 1024)}KB) exceeds maximum allowed (${Math.round(
238
+ effectiveLimits.maxPromptBytes / 1024
239
+ )}KB)`,
240
+ details: { promptBytes: bytes }
241
+ };
242
+ }
243
+ return { valid: true, details: { promptBytes: bytes } };
244
+ }
245
+ var SENSITIVE_PATTERNS = [
246
+ /zhn_(?:live|test|dev)_[a-zA-Z0-9]+/g,
247
+ // API keys
248
+ /Bearer\s+[a-zA-Z0-9._-]+/gi,
249
+ // Bearer tokens
250
+ /at\s+[\w./<>]+\s*\(.*:\d+:\d+\)/g,
251
+ // Stack trace lines
252
+ /^\s+at\s+.+$/gm,
253
+ // Stack traces
254
+ /Error:\s*.*\n\s+at/g
255
+ // Error stack beginnings
256
+ ];
257
+ var ERROR_CODE_MAP = {
258
+ RATE_LIMIT_EXCEEDED: "Rate limit exceeded. Please wait before retrying.",
259
+ UNAUTHORIZED: "Authentication failed. Please check your API credentials.",
260
+ FORBIDDEN: "This API key's role does not allow this action. The required role depends on the endpoint \u2014 check the key's role in Zihin \u2192 Settings \u2192 API Keys.",
261
+ INVALID_MODEL: "The specified model is not available.",
262
+ INTERNAL_ERROR: "An internal error occurred. Please try again later.",
263
+ TIMEOUT: "Request timed out. Try increasing the timeout.",
264
+ INVALID_REQUEST: "The request was malformed. Please check your input.",
265
+ INSUFFICIENT_CREDITS: "Insufficient credits. Please check your account balance.",
266
+ CONTEXT_LENGTH_EXCEEDED: "The input exceeds the model context length limit.",
267
+ AGENT_NOT_FOUND: "Agent not found or not active for this tenant.",
268
+ // Bloqueios comerciais. As mensagens do BE vêm em português e estes shells são em inglês;
269
+ // além disso cada caso tem uma ação DIFERENTE, e o texto precisa dizer qual.
270
+ TRIAL_EXPIRED: "Your Zihin trial has ended. Upgrade the plan to keep invoking agents \u2014 the API key is fine.",
271
+ QUOTA_EXCEEDED: "Your Zihin token quota is exhausted. Upgrade the plan or wait for the next cycle \u2014 retrying will not help, and the API key is fine.",
272
+ NO_ACTIVE_SUBSCRIPTION: "This tenant has no active Zihin subscription. Activate a plan to invoke agents \u2014 the API key is fine."
273
+ };
274
+ function sanitizeApiError(error) {
275
+ let message = "An unexpected error occurred";
276
+ let code;
277
+ let retryable = false;
278
+ let retryAfter;
279
+ if (typeof error === "object" && error !== null) {
280
+ const err = error;
281
+ const nested = err.error && typeof err.error === "object" ? err.error : void 0;
282
+ const codeRaw = nested?.code ?? (typeof err.error === "string" ? err.error : err.code);
283
+ code = typeof codeRaw === "string" ? codeRaw : void 0;
284
+ const rawMessage = nested?.message ?? err.message;
285
+ const reason = typeof err.reason === "string" ? err.reason : void 0;
286
+ const effectiveCode = code === "QUOTA_EXCEEDED" && reason === "no_active_subscription" ? "NO_ACTIVE_SUBSCRIPTION" : code;
287
+ if (effectiveCode && ERROR_CODE_MAP[effectiveCode]) {
288
+ message = ERROR_CODE_MAP[effectiveCode];
289
+ } else if (typeof rawMessage === "string" && rawMessage) {
290
+ message = rawMessage;
291
+ }
292
+ if (code === "RATE_LIMIT_EXCEEDED" || code === "TIMEOUT") {
293
+ retryable = true;
294
+ retryAfter = err.retryAfter || 60;
295
+ }
296
+ if (message.length > 2e3) message = message.slice(0, 2e3);
297
+ for (const pattern of SENSITIVE_PATTERNS) {
298
+ message = message.replace(pattern, "[REDACTED]");
299
+ }
300
+ } else if (typeof error === "string") {
301
+ message = error;
302
+ if (message.length > 2e3) message = message.slice(0, 2e3);
303
+ for (const pattern of SENSITIVE_PATTERNS) {
304
+ message = message.replace(pattern, "[REDACTED]");
305
+ }
306
+ }
307
+ if (message.length > 500) {
308
+ message = message.substring(0, 500) + "...";
309
+ }
310
+ return { message, code, retryable, retryAfter };
311
+ }
312
+ var API_KEY_PATTERN = /^zhn_(live|test|dev)_[a-zA-Z0-9]{20,}$/;
313
+ function validateApiKeyFormat(apiKey) {
314
+ if (!apiKey || apiKey.trim() === "") {
315
+ return { valid: false, error: "API key is required" };
316
+ }
317
+ const trimmed = apiKey.trim();
318
+ if (!API_KEY_PATTERN.test(trimmed)) {
319
+ return {
320
+ valid: false,
321
+ error: "Invalid API key format. Expected: zhn_live_xxx, zhn_test_xxx or zhn_dev_xxx"
322
+ };
323
+ }
324
+ const environment = trimmed.startsWith("zhn_live_") ? "live" : trimmed.startsWith("zhn_test_") ? "test" : "dev";
325
+ return { valid: true, environment };
326
+ }
327
+
328
+ // src/errors.ts
329
+ var ZihinAgentError = class _ZihinAgentError extends Error {
330
+ constructor(kind, message, opts = {}) {
331
+ super(buildMessage(message, opts));
332
+ this.name = "ZihinAgentError";
333
+ this.kind = kind;
334
+ this.status = opts.status;
335
+ this.code = opts.code;
336
+ this.requestId = opts.requestId;
337
+ this.executionId = opts.executionId;
338
+ this.attempts = opts.attempts;
339
+ this.endpoint = opts.endpoint;
340
+ this.retryable = opts.retryable ?? (kind === "rate_limit" || kind === "server" || kind === "network");
341
+ Object.setPrototypeOf(this, _ZihinAgentError.prototype);
342
+ }
343
+ };
344
+ function buildMessage(base, opts) {
345
+ let s = base;
346
+ if (opts.endpoint) s += ` [${opts.endpoint}]`;
347
+ if (opts.attempts && opts.attempts > 1) s += ` (after ${opts.attempts} attempts)`;
348
+ if (opts.requestId) s += ` (request_id: ${opts.requestId})`;
349
+ if (opts.executionId) s += ` (execution_id: ${opts.executionId})`;
350
+ return s;
351
+ }
352
+ var COMMERCIAL_BLOCK_CODES = /* @__PURE__ */ new Set(["TRIAL_EXPIRED", "QUOTA_EXCEEDED", "NO_ACTIVE_SUBSCRIPTION"]);
353
+ function errorCodeOf(body) {
354
+ const b = rec(body);
355
+ return str(b.error) ?? str(rec(b.error).code) ?? str(b.code);
356
+ }
357
+ function isCommercialBlock(body) {
358
+ const code = errorCodeOf(body);
359
+ return code !== void 0 && COMMERCIAL_BLOCK_CODES.has(code);
360
+ }
361
+ function commercialKind(body) {
362
+ const code = errorCodeOf(body);
363
+ if (code === "TRIAL_EXPIRED") return "trial_expired";
364
+ if (code === "NO_ACTIVE_SUBSCRIPTION") return "no_subscription";
365
+ if (code === "QUOTA_EXCEEDED") {
366
+ return str(rec(body).reason) === "no_active_subscription" ? "no_subscription" : "quota_exceeded";
367
+ }
368
+ return void 0;
369
+ }
370
+ function extractRequestId(body) {
371
+ const b = rec(body);
372
+ return str(b.request_id) ?? str(b.requestId) ?? str(rec(b.error).request_id) ?? str(rec(b.meta).request_id);
373
+ }
374
+ function errorFromResponse(status, body, meta = {}) {
375
+ const sanitized = sanitizeApiError(body);
376
+ const message = sanitized.message;
377
+ const code = sanitized.code;
378
+ const base = {
379
+ status,
380
+ code,
381
+ requestId: extractRequestId(body),
382
+ executionId: str(rec(body).execution_id),
383
+ ...meta
384
+ };
385
+ const commercial = commercialKind(body);
386
+ if (commercial) {
387
+ return new ZihinAgentError(commercial, message || "Request blocked by your plan.", {
388
+ ...base,
389
+ retryable: false
390
+ });
391
+ }
392
+ if (status === 401) return new ZihinAgentError("auth", message || "Authentication failed.", base);
393
+ if (status === 403)
394
+ return new ZihinAgentError(
395
+ "auth",
396
+ // Não enumerar papéis aqui: o papel exigido varia por rota (triggers aceitam editor,
397
+ // mas delegar/cancelar uma task exige admin/owner). A frase antiga mandava o usuário
398
+ // para o papel errado em metade da API.
399
+ message || "This API key's role does not allow this action. The required role depends on the endpoint \u2014 check the key's role in Zihin \u2192 Settings \u2192 API Keys.",
400
+ base
401
+ );
402
+ if (status === 404) return new ZihinAgentError("not_found", message || "Resource not found.", base);
403
+ if (status === 408)
404
+ return new ZihinAgentError("timeout", message || "The server timed out handling the request.", {
405
+ ...base,
406
+ retryable: true
407
+ });
408
+ if (status === 429)
409
+ return new ZihinAgentError("rate_limit", message || "Rate limit exceeded.", { ...base, retryable: true });
410
+ if (status >= 500)
411
+ return new ZihinAgentError("server", message || `Server error (${status}).`, { ...base, retryable: true });
412
+ return new ZihinAgentError("unknown", message || `Request failed (${status}).`, base);
413
+ }
414
+ var NON_RETRYABLE_NET = /^(ENOTFOUND|EAI_AGAIN|CERT_|ERR_TLS|UNABLE_TO_VERIFY|DEPTH_ZERO)/;
415
+ function errorFromThrow(err, timeoutMs, meta = {}) {
416
+ if (err instanceof ZihinAgentError) return err;
417
+ const name = err?.name;
418
+ if (name === "TimeoutError") {
419
+ return new ZihinAgentError("timeout", `Request timed out after ${timeoutMs ?? "?"}ms.`, {
420
+ ...meta,
421
+ retryable: false
422
+ });
423
+ }
424
+ if (name === "AbortError") {
425
+ return new ZihinAgentError("aborted", "Request was aborted by the caller.", { ...meta, retryable: false });
426
+ }
427
+ const cause = err?.cause;
428
+ const causeCode = cause?.code;
429
+ const sanitized = sanitizeApiError(err);
430
+ const detail = causeCode || sanitized.message || "Network error";
431
+ const retryable = causeCode ? !NON_RETRYABLE_NET.test(causeCode) : true;
432
+ return new ZihinAgentError("network", `Network error: ${detail}`, { ...meta, retryable });
433
+ }
434
+
435
+ // src/http.ts
436
+ var DEFAULT_BASE_URL = "https://llm.zihin.ai";
437
+ var OPERATION_TIMEOUT_MS = Object.freeze({
438
+ stream: 2e5,
439
+ dispatch: 26e4,
440
+ short: 35e3
441
+ });
442
+ var DEADLINE_GRACE_MS = 1e4;
443
+ var RETRY_OVERHEAD_MS = 6e4;
444
+ var MIN_TIMEOUT_MS = 1e3;
445
+ var MAX_TIMEOUT_MS = 9e5;
446
+ function normalizeTimeoutMs(value, label = "timeoutMs") {
447
+ const n = typeof value === "number" ? value : Number(value);
448
+ if (!Number.isFinite(n) || n <= 0) {
449
+ throw new ZihinAgentError(
450
+ "config",
451
+ `${label} must be a positive number of milliseconds (got ${JSON.stringify(value)}).`
452
+ );
453
+ }
454
+ return Math.min(Math.max(n, MIN_TIMEOUT_MS), MAX_TIMEOUT_MS);
455
+ }
456
+ var DEFAULT_MAX_RETRIES = 2;
457
+ var DEFAULT_MAX_EVENT_BYTES = 1 * 1024 * 1024;
458
+ var DEFAULT_MAX_TOTAL_BYTES = 64 * 1024 * 1024;
459
+ function resolveFetch(injected) {
460
+ const f = injected ?? globalThis.fetch;
461
+ if (typeof f !== "function") {
462
+ throw new ZihinAgentError("config", "Global fetch is unavailable in this runtime.");
463
+ }
464
+ return f;
465
+ }
466
+ function resolveConfig(config) {
467
+ const keyCheck = validateApiKeyFormat(config.apiKey ?? "");
468
+ if (!keyCheck.valid) {
469
+ throw new ZihinAgentError("config", keyCheck.error || "Invalid API key.");
470
+ }
471
+ const urlCheck = validateBaseUrl(config.baseUrl ?? "", {
472
+ allowCustomDomains: config.allowCustomBaseUrl,
473
+ allowHttp: config.allowHttp,
474
+ allowLocalhost: config.allowHttp
475
+ });
476
+ if (!urlCheck.valid) {
477
+ throw new ZihinAgentError("config", urlCheck.error || "Invalid base URL.");
478
+ }
479
+ if (typeof (config.fetch ?? globalThis.fetch) !== "function") {
480
+ throw new ZihinAgentError(
481
+ "config",
482
+ "Global fetch is unavailable in this runtime. Use Node >=18 or pass a `fetch` implementation in the client config."
483
+ );
484
+ }
485
+ return {
486
+ apiKey: config.apiKey.trim(),
487
+ baseUrl: urlCheck.sanitizedUrl || DEFAULT_BASE_URL,
488
+ timeoutMs: config.timeoutMs === void 0 || config.timeoutMs === null ? void 0 : normalizeTimeoutMs(config.timeoutMs, "timeoutMs"),
489
+ maxRetries: config.maxRetries ?? DEFAULT_MAX_RETRIES,
490
+ maxStreamEventBytes: config.maxStreamEventBytes ?? DEFAULT_MAX_EVENT_BYTES,
491
+ maxStreamTotalBytes: config.maxStreamTotalBytes ?? DEFAULT_MAX_TOTAL_BYTES,
492
+ fetchImpl: config.fetch,
493
+ onRetry: config.onRetry,
494
+ onError: config.onError
495
+ };
496
+ }
497
+ function resolveTimeout(cfg, operation) {
498
+ return cfg.timeoutMs ?? OPERATION_TIMEOUT_MS[operation];
499
+ }
500
+ function endpointLabel(method, url) {
501
+ try {
502
+ const u = new URL(url);
503
+ return `${method} ${u.host}${u.pathname}`;
504
+ } catch {
505
+ return method;
506
+ }
507
+ }
508
+ function retryAfterMs(headers, capMs = 3e4) {
509
+ const raw = headers.get("retry-after");
510
+ if (!raw) return void 0;
511
+ const secs = Number(raw);
512
+ if (Number.isFinite(secs)) return Math.min(Math.max(0, secs * 1e3), capMs);
513
+ const when = Date.parse(raw);
514
+ if (Number.isFinite(when)) return Math.min(Math.max(0, when - Date.now()), capMs);
515
+ return void 0;
516
+ }
517
+ function buildHeaders(apiKey, extra = {}) {
518
+ return {
519
+ "Content-Type": "application/json",
520
+ Accept: "application/json",
521
+ "X-Api-Key": apiKey,
522
+ ...extra
523
+ };
524
+ }
525
+ function delay(ms, signal) {
526
+ return new Promise((resolve, reject) => {
527
+ if (signal?.aborted) {
528
+ reject(new ZihinAgentError("aborted", "Request was aborted by the caller."));
529
+ return;
530
+ }
531
+ const onAbort = () => {
532
+ clearTimeout(timer);
533
+ reject(new ZihinAgentError("aborted", "Request was aborted by the caller."));
534
+ };
535
+ const timer = setTimeout(() => {
536
+ if (signal) signal.removeEventListener("abort", onAbort);
537
+ resolve();
538
+ }, ms);
539
+ if (signal) signal.addEventListener("abort", onAbort, { once: true });
540
+ });
541
+ }
542
+ function backoffMs(attempt) {
543
+ const base = Math.min(1e3 * 2 ** attempt, 1e4);
544
+ return base / 2 + Math.random() * (base / 2);
545
+ }
546
+ function createDeadline(timeoutMs, callerSignal) {
547
+ const controller = new AbortController();
548
+ let timedOut = false;
549
+ let abortedByCaller = false;
550
+ const timer = setTimeout(() => {
551
+ timedOut = true;
552
+ controller.abort();
553
+ }, timeoutMs);
554
+ const onAbort = () => {
555
+ abortedByCaller = true;
556
+ controller.abort();
557
+ };
558
+ if (callerSignal) {
559
+ if (callerSignal.aborted) onAbort();
560
+ else callerSignal.addEventListener("abort", onAbort, { once: true });
561
+ }
562
+ return {
563
+ signal: controller.signal,
564
+ dispose: () => {
565
+ clearTimeout(timer);
566
+ if (callerSignal) callerSignal.removeEventListener("abort", onAbort);
567
+ },
568
+ timedOut: () => timedOut,
569
+ abortedByCaller: () => abortedByCaller
570
+ };
571
+ }
572
+ function shouldRetryStatus(status, policy) {
573
+ if (policy === "idempotent") return status >= 500 || status === 429 || status === 408;
574
+ return status === 429 || status === 503;
575
+ }
576
+ function budgetTracker(args) {
577
+ const startedAt = Date.now();
578
+ const reported = args.reportedTimeoutMs ?? args.timeoutMs;
579
+ const overheadCap = Math.min(args.timeoutMs, RETRY_OVERHEAD_MS);
580
+ return {
581
+ reported,
582
+ /** Quanto ainda pode ser gasto com tentativas falhas + backoff. */
583
+ overheadLeft: () => overheadCap - (Date.now() - startedAt),
584
+ expired: (attempts) => new ZihinAgentError("timeout", `Request timed out after ${reported}ms.`, {
585
+ endpoint: args.endpoint,
586
+ attempts
587
+ })
588
+ };
589
+ }
590
+ async function requestBuffered(args) {
591
+ const endpoint = endpointLabel(args.method, args.url);
592
+ const doFetch = resolveFetch(args.fetchImpl);
593
+ const budget = budgetTracker({ ...args, endpoint });
594
+ let lastError = null;
595
+ for (let attempt = 0; attempt <= args.maxRetries; attempt++) {
596
+ const d = createDeadline(args.timeoutMs, args.callerSignal);
597
+ try {
598
+ const res = await doFetch(args.url, {
599
+ method: args.method,
600
+ headers: args.headers,
601
+ body: args.body,
602
+ signal: d.signal,
603
+ redirect: "error",
604
+ // 3xx para outro host vazaria a X-Api-Key
605
+ cache: "no-store"
606
+ // evita cache stale (ex.: polling em Next.js/edge)
607
+ });
608
+ let wait = shouldRetryStatus(res.status, args.policy) && attempt < args.maxRetries ? retryAfterMs(res.headers) ?? backoffMs(attempt) : null;
609
+ let peeked;
610
+ if (wait !== null && res.status === 429) {
611
+ peeked = await res.text();
612
+ if (isCommercialBlock(parseBody(peeked))) wait = null;
613
+ }
614
+ if (wait !== null && budget.overheadLeft() > wait) {
615
+ if (peeked === void 0) {
616
+ try {
617
+ await res.body?.cancel();
618
+ } catch {
619
+ }
620
+ }
621
+ d.dispose();
622
+ args.onRetry?.({ endpoint, attempt: attempt + 1, delayMs: wait, reason: "status", status: res.status });
623
+ await delay(wait, args.callerSignal);
624
+ continue;
625
+ }
626
+ const text = peeked ?? await res.text();
627
+ d.dispose();
628
+ return { status: res.status, ok: res.ok, text, endpoint, attempts: attempt + 1 };
629
+ } catch (err) {
630
+ d.dispose();
631
+ if (err instanceof ZihinAgentError) throw err;
632
+ const meta = { endpoint, attempts: attempt + 1 };
633
+ if (d.timedOut()) throw budget.expired(attempt + 1);
634
+ if (d.abortedByCaller())
635
+ throw new ZihinAgentError("aborted", "Request was aborted by the caller.", meta);
636
+ lastError = errorFromThrow(err, budget.reported, meta);
637
+ const canRetry = args.policy === "idempotent" && lastError.retryable && attempt < args.maxRetries;
638
+ if (!canRetry) throw lastError;
639
+ const wait = backoffMs(attempt);
640
+ if (budget.overheadLeft() <= wait) throw lastError;
641
+ args.onRetry?.({ endpoint, attempt: attempt + 1, delayMs: wait, reason: "network" });
642
+ await delay(wait, args.callerSignal);
643
+ }
644
+ }
645
+ throw lastError ?? new ZihinAgentError("unknown", "Max retries exceeded.", { endpoint });
646
+ }
647
+ async function openStream(args) {
648
+ const endpoint = endpointLabel("POST", args.url);
649
+ const doFetch = resolveFetch(args.fetchImpl);
650
+ const budget = budgetTracker({ ...args, endpoint });
651
+ for (let attempt = 0; attempt <= args.maxRetries; attempt++) {
652
+ const d = createDeadline(args.timeoutMs, args.callerSignal);
653
+ try {
654
+ const response = await doFetch(args.url, {
655
+ method: "POST",
656
+ headers: { ...args.headers, Accept: "text/event-stream" },
657
+ body: args.body,
658
+ signal: d.signal,
659
+ redirect: "error",
660
+ cache: "no-store"
661
+ });
662
+ let wait = shouldRetryStatus(response.status, "side-effecting") && attempt < args.maxRetries ? retryAfterMs(response.headers) ?? backoffMs(attempt) : null;
663
+ if (response.status === 429) {
664
+ const text = await response.text().catch(() => "");
665
+ const body = parseBody(text);
666
+ if (wait === null || isCommercialBlock(body) || budget.overheadLeft() <= wait) {
667
+ d.dispose();
668
+ throw errorFromResponse(response.status, body, { endpoint, attempts: attempt + 1 });
669
+ }
670
+ d.dispose();
671
+ args.onRetry?.({ endpoint, attempt: attempt + 1, delayMs: wait, reason: "status", status: 429 });
672
+ await delay(wait, args.callerSignal);
673
+ continue;
674
+ }
675
+ if (wait !== null && budget.overheadLeft() > wait) {
676
+ try {
677
+ await response.body?.cancel();
678
+ } catch {
679
+ }
680
+ d.dispose();
681
+ args.onRetry?.({
682
+ endpoint,
683
+ attempt: attempt + 1,
684
+ delayMs: wait,
685
+ reason: "status",
686
+ status: response.status
687
+ });
688
+ await delay(wait, args.callerSignal);
689
+ continue;
690
+ }
691
+ return { response, deadline: d, endpoint };
692
+ } catch (err) {
693
+ d.dispose();
694
+ if (err instanceof ZihinAgentError) throw err;
695
+ const meta = { endpoint, attempts: attempt + 1 };
696
+ if (d.timedOut()) throw budget.expired(attempt + 1);
697
+ if (d.abortedByCaller())
698
+ throw new ZihinAgentError("aborted", "Request was aborted by the caller.", meta);
699
+ throw errorFromThrow(err, budget.reported, meta);
700
+ }
701
+ }
702
+ throw new ZihinAgentError("unknown", "Max retries exceeded.", { endpoint });
703
+ }
704
+ function toJsonBody(value, label = "body") {
705
+ try {
706
+ return JSON.stringify(value);
707
+ } catch (err) {
708
+ throw new ZihinAgentError(
709
+ "config",
710
+ `${label} is not JSON-serializable: ${err?.message ?? "unknown reason"}.`
711
+ );
712
+ }
713
+ }
714
+ async function requestJson(cfg, args) {
715
+ const res = await requestBuffered({
716
+ method: args.method,
717
+ url: `${cfg.baseUrl}${args.path}`,
718
+ headers: buildHeaders(cfg.apiKey),
719
+ body: args.body !== void 0 ? toJsonBody(args.body) : void 0,
720
+ timeoutMs: resolveTimeout(cfg, args.operation ?? "short"),
721
+ maxRetries: cfg.maxRetries,
722
+ policy: args.policy,
723
+ callerSignal: args.signal,
724
+ fetchImpl: cfg.fetchImpl,
725
+ onRetry: cfg.onRetry
726
+ });
727
+ if (!res.ok) {
728
+ throw errorFromResponse(res.status, parseBody(res.text), {
729
+ endpoint: res.endpoint,
730
+ attempts: res.attempts
731
+ });
732
+ }
733
+ try {
734
+ return JSON.parse(res.text);
735
+ } catch {
736
+ throw new ZihinAgentError("protocol", "Expected JSON response.", { endpoint: res.endpoint });
737
+ }
738
+ }
739
+ function parseBody(text) {
740
+ if (!text) return {};
741
+ try {
742
+ return JSON.parse(text);
743
+ } catch {
744
+ return { message: text };
745
+ }
746
+ }
747
+
748
+ // src/agents.ts
749
+ async function listAgents(cfg, opts = {}) {
750
+ const res = await requestBuffered({
751
+ method: "GET",
752
+ url: `${cfg.baseUrl}/api/v2/agents`,
753
+ headers: buildHeaders(cfg.apiKey),
754
+ timeoutMs: resolveTimeout(cfg, "short"),
755
+ maxRetries: cfg.maxRetries,
756
+ policy: "idempotent",
757
+ callerSignal: opts.signal,
758
+ fetchImpl: cfg.fetchImpl,
759
+ onRetry: cfg.onRetry
760
+ });
761
+ if (!res.ok) {
762
+ throw errorFromResponse(res.status, parseBody(res.text), { endpoint: res.endpoint, attempts: res.attempts });
763
+ }
764
+ let json;
765
+ try {
766
+ json = JSON.parse(res.text);
767
+ } catch {
768
+ throw new ZihinAgentError("protocol", "Expected JSON from GET /api/v2/agents.");
769
+ }
770
+ const data = rec(json).data;
771
+ const list = Array.isArray(data) ? data : [];
772
+ return list.map((item) => {
773
+ const a = rec(item);
774
+ return {
775
+ id: str(a.id) ?? "",
776
+ name: str(a.name),
777
+ commercialName: str(a.commercial_name),
778
+ bio: str(a.bio),
779
+ type: str(a.type),
780
+ status: str(a.status),
781
+ tags: Array.isArray(a.tags) ? a.tags.filter((t) => typeof t === "string") : void 0,
782
+ toolsCount: typeof a.tools_count === "number" ? a.tools_count : void 0
783
+ };
784
+ });
785
+ }
786
+
787
+ // src/sse.ts
788
+ var DEFAULT_MAX_EVENT_BYTES2 = 1 * 1024 * 1024;
789
+ var DEFAULT_MAX_TOTAL_BYTES2 = 64 * 1024 * 1024;
790
+ async function* parseSse(response, opts = {}) {
791
+ if (!response.body) {
792
+ throw new ZihinAgentError("protocol", "SSE response has no body.");
793
+ }
794
+ const maxEventBytes = opts.maxEventBytes ?? DEFAULT_MAX_EVENT_BYTES2;
795
+ const maxTotalBytes = opts.maxTotalBytes ?? DEFAULT_MAX_TOTAL_BYTES2;
796
+ const reader = response.body.getReader();
797
+ const decoder = new TextDecoder("utf-8");
798
+ let buffer = "";
799
+ let eventName = "";
800
+ let dataLines = [];
801
+ let dataBytes = 0;
802
+ let totalBytes = 0;
803
+ const flush = () => {
804
+ const had = dataLines.length > 0 || eventName !== "";
805
+ const rawData = dataLines.join("\n");
806
+ const evt = eventName || "message";
807
+ eventName = "";
808
+ dataLines = [];
809
+ dataBytes = 0;
810
+ if (!had || rawData === "") return null;
811
+ let data = rawData;
812
+ try {
813
+ data = JSON.parse(rawData);
814
+ } catch {
815
+ }
816
+ return { event: evt, data, rawData };
817
+ };
818
+ try {
819
+ while (true) {
820
+ if (opts.signal?.aborted) {
821
+ throw opts.onAbortError ? opts.onAbortError() : new ZihinAgentError("aborted", "Stream aborted.");
822
+ }
823
+ let chunk;
824
+ try {
825
+ chunk = await reader.read();
826
+ } catch (err) {
827
+ throw opts.onAbortError ? opts.onAbortError() : errorFromThrow(err);
828
+ }
829
+ if (chunk.done) break;
830
+ totalBytes += chunk.value.byteLength;
831
+ if (totalBytes > maxTotalBytes) {
832
+ throw new ZihinAgentError("protocol", `SSE stream exceeded ${maxTotalBytes} bytes.`);
833
+ }
834
+ buffer += decoder.decode(chunk.value, { stream: true });
835
+ let nl;
836
+ while ((nl = buffer.indexOf("\n")) !== -1) {
837
+ let line = buffer.slice(0, nl);
838
+ buffer = buffer.slice(nl + 1);
839
+ if (line.endsWith("\r")) line = line.slice(0, -1);
840
+ if (line === "") {
841
+ const e = flush();
842
+ if (e) yield e;
843
+ continue;
844
+ }
845
+ if (line.startsWith(":")) continue;
846
+ const colon = line.indexOf(":");
847
+ const field = colon === -1 ? line : line.slice(0, colon);
848
+ let val = colon === -1 ? "" : line.slice(colon + 1);
849
+ if (val.startsWith(" ")) val = val.slice(1);
850
+ if (field === "event") {
851
+ eventName = val;
852
+ } else if (field === "data") {
853
+ dataBytes += utf8Len(val) + 1;
854
+ if (dataBytes > maxEventBytes) {
855
+ throw new ZihinAgentError("protocol", `SSE event exceeded ${maxEventBytes} bytes.`);
856
+ }
857
+ dataLines.push(val);
858
+ }
859
+ }
860
+ if (utf8Len(buffer) > maxEventBytes) {
861
+ throw new ZihinAgentError("protocol", `SSE line exceeded ${maxEventBytes} bytes.`);
862
+ }
863
+ }
864
+ buffer += decoder.decode();
865
+ if (buffer.length > 0) {
866
+ let line = buffer.endsWith("\r") ? buffer.slice(0, -1) : buffer;
867
+ if (line && !line.startsWith(":")) {
868
+ const colon = line.indexOf(":");
869
+ const field = colon === -1 ? line : line.slice(0, colon);
870
+ let val = colon === -1 ? "" : line.slice(colon + 1);
871
+ if (val.startsWith(" ")) val = val.slice(1);
872
+ if (field === "data") dataLines.push(val);
873
+ else if (field === "event") eventName = val;
874
+ }
875
+ }
876
+ const tail = flush();
877
+ if (tail) yield tail;
878
+ } finally {
879
+ try {
880
+ await reader.cancel();
881
+ } catch {
882
+ }
883
+ opts.onEnd?.();
884
+ }
885
+ }
886
+
887
+ // src/invoke.ts
888
+ function mapEvent(e) {
889
+ const d = rec(e.data);
890
+ switch (e.event) {
891
+ case "metadata":
892
+ return {
893
+ type: "metadata",
894
+ requestId: str(d.request_id),
895
+ agentId: str(d.agent_id),
896
+ sessionId: str(d.session_id),
897
+ data: e.data
898
+ };
899
+ case "status":
900
+ return { type: "status", phase: str(d.phase), data: e.data };
901
+ case "attachments":
902
+ return { type: "attachments", data: e.data };
903
+ case "phase":
904
+ return { type: "phase", phase: str(d.phase), data: e.data };
905
+ case "thinking":
906
+ return { type: "thinking", thought: str(d.thought), data: e.data };
907
+ case "tool":
908
+ return {
909
+ type: "tool",
910
+ tool: str(d.tool),
911
+ callId: str(d.call_id),
912
+ success: typeof d.success === "boolean" ? d.success : void 0,
913
+ data: e.data
914
+ };
915
+ case "action_proposed":
916
+ return { type: "action_proposed", data: e.data };
917
+ case "token":
918
+ return { type: "token", content: str(d.content) ?? "", data: e.data };
919
+ case "warning":
920
+ return { type: "warning", message: str(d.message), data: e.data };
921
+ case "session":
922
+ return { type: "session", sessionId: str(d.session_id), data: e.data };
923
+ case "done":
924
+ return { type: "done", data: e.data };
925
+ case "error": {
926
+ const err = rec(d.error);
927
+ return {
928
+ type: "error",
929
+ code: str(err.code),
930
+ message: str(err.message),
931
+ requestId: str(err.request_id),
932
+ data: e.data
933
+ };
934
+ }
935
+ case "response":
936
+ return {
937
+ type: "response",
938
+ content: str(d.content) ?? "",
939
+ sessionId: str(d.session_id),
940
+ sources: Array.isArray(d.sources) ? d.sources : void 0,
941
+ blocks: Array.isArray(d.blocks) ? d.blocks : void 0,
942
+ data: e.data
943
+ };
944
+ case "metrics": {
945
+ const modelConfig = rec(d.model_config);
946
+ return {
947
+ type: "metrics",
948
+ usage: parseUsage(d.usage),
949
+ model: str(modelConfig.model) ?? str(d.model),
950
+ iterations: num(d.iterations),
951
+ executionTimeMs: num(d.execution_time_ms),
952
+ data: e.data
953
+ };
954
+ }
955
+ default:
956
+ return { type: "unknown", event: e.event, data: e.data };
957
+ }
958
+ }
959
+ function buildRequestBody(params, normalizedTimeoutMs) {
960
+ const body = {};
961
+ if (params.message !== void 0) body.message = params.message;
962
+ if (params.sessionId) body.session_id = params.sessionId;
963
+ if (params.attachments?.length) {
964
+ body.attachments = params.attachments.map((a) => ({ url: a.url, type: a.type }));
965
+ }
966
+ if (params.options) {
967
+ const o = {};
968
+ if (params.options.temperature !== void 0) o.temperature = params.options.temperature;
969
+ if (params.options.maxTokens !== void 0) o.max_tokens = params.options.maxTokens;
970
+ if (normalizedTimeoutMs !== void 0) o.timeout_ms = normalizedTimeoutMs;
971
+ if (params.options.jitMode !== void 0) o.jit_mode = params.options.jitMode;
972
+ if (Object.keys(o).length) body.options = o;
973
+ }
974
+ if (params.metadata) {
975
+ const m = { ...params.metadata };
976
+ if (params.metadata.traceId !== void 0) {
977
+ m.trace_id = params.metadata.traceId;
978
+ delete m.traceId;
979
+ }
980
+ if (params.metadata.correlationId !== void 0) {
981
+ m.correlation_id = params.metadata.correlationId;
982
+ delete m.correlationId;
983
+ }
984
+ body.metadata = m;
985
+ }
986
+ return body;
987
+ }
988
+ function validateParams(params) {
989
+ if (!params.agentId) {
990
+ throw new ZihinAgentError("config", "agentId is required.");
991
+ }
992
+ if (!params.message && !(params.attachments && params.attachments.length)) {
993
+ throw new ZihinAgentError("config", "Either `message` or `attachments` is required.");
994
+ }
995
+ if (params.message) {
996
+ const sizeCheck = validatePromptSize(params.message);
997
+ if (!sizeCheck.valid) {
998
+ throw new ZihinAgentError("config", sizeCheck.error || "Message too large.");
999
+ }
1000
+ }
1001
+ if (params.options?.timeoutMs !== void 0) {
1002
+ normalizeTimeoutMs(params.options.timeoutMs, "options.timeoutMs");
1003
+ }
1004
+ for (const att of params.attachments ?? []) {
1005
+ const urlCheck = validateBaseUrl(att.url, { allowCustomDomains: true });
1006
+ if (!urlCheck.valid) {
1007
+ throw new ZihinAgentError("config", `Invalid attachment URL: ${urlCheck.error}`);
1008
+ }
1009
+ }
1010
+ }
1011
+ async function* streamAgent(cfg, params) {
1012
+ validateParams(params);
1013
+ const callerTimeoutMs = params.options?.timeoutMs !== void 0 ? normalizeTimeoutMs(params.options.timeoutMs, "options.timeoutMs") : void 0;
1014
+ const timeoutMs = callerTimeoutMs !== void 0 ? callerTimeoutMs + DEADLINE_GRACE_MS : resolveTimeout(cfg, "stream");
1015
+ const reportedMs = callerTimeoutMs ?? timeoutMs;
1016
+ const url = `${cfg.baseUrl}/api/v2/agents/${encodeURIComponent(params.agentId)}/stream`;
1017
+ const { response, deadline, endpoint } = await openStream({
1018
+ url,
1019
+ headers: buildHeaders(cfg.apiKey),
1020
+ body: toJsonBody(buildRequestBody(params, callerTimeoutMs), "request body"),
1021
+ timeoutMs,
1022
+ reportedTimeoutMs: reportedMs,
1023
+ maxRetries: cfg.maxRetries,
1024
+ callerSignal: params.signal,
1025
+ fetchImpl: cfg.fetchImpl,
1026
+ onRetry: cfg.onRetry
1027
+ });
1028
+ try {
1029
+ if (!response.ok) {
1030
+ let text = "";
1031
+ try {
1032
+ text = await response.text();
1033
+ } catch {
1034
+ }
1035
+ throw errorFromResponse(response.status, parseBody(text), { endpoint });
1036
+ }
1037
+ const onAbortError = () => deadline.timedOut() ? new ZihinAgentError("timeout", `Stream timed out after ${reportedMs}ms.`, { endpoint }) : deadline.abortedByCaller() ? new ZihinAgentError("aborted", "Stream aborted by the caller.", { endpoint }) : new ZihinAgentError("network", "Stream connection interrupted.", { endpoint });
1038
+ for await (const e of parseSse(response, {
1039
+ signal: params.signal,
1040
+ onAbortError,
1041
+ maxEventBytes: cfg.maxStreamEventBytes,
1042
+ maxTotalBytes: cfg.maxStreamTotalBytes
1043
+ })) {
1044
+ yield mapEvent(e);
1045
+ }
1046
+ } finally {
1047
+ deadline.dispose();
1048
+ }
1049
+ }
1050
+ async function invokeAgent(cfg, params) {
1051
+ const raw = [];
1052
+ const keepRaw = params.includeRaw === true;
1053
+ let responseChunk;
1054
+ let metricsChunk;
1055
+ let metadataSessionId;
1056
+ let mintedSessionId;
1057
+ let cleanEnd = false;
1058
+ let tokenBuffer = "";
1059
+ for await (const chunk of streamAgent(cfg, params)) {
1060
+ if (keepRaw) raw.push(chunk);
1061
+ if (chunk.type === "error") {
1062
+ const code = chunk.code ?? "AGENT_ERROR";
1063
+ const kind = /TIMEOUT/i.test(code) ? "timeout" : "server";
1064
+ throw new ZihinAgentError(kind, sanitizeApiError(chunk.message ?? code).message || code, {
1065
+ code,
1066
+ requestId: chunk.requestId,
1067
+ retryable: false
1068
+ });
1069
+ }
1070
+ if (chunk.type === "response") responseChunk = chunk;
1071
+ else if (chunk.type === "metrics") metricsChunk = chunk;
1072
+ else if (chunk.type === "token") tokenBuffer += chunk.content;
1073
+ else if (chunk.type === "metadata" && chunk.sessionId) metadataSessionId = chunk.sessionId;
1074
+ else if (chunk.type === "session" && chunk.sessionId) mintedSessionId = chunk.sessionId;
1075
+ else if (chunk.type === "done") cleanEnd = true;
1076
+ }
1077
+ if (!responseChunk) {
1078
+ if (cleanEnd) {
1079
+ throw new ZihinAgentError("protocol", "Stream ended (done) without a `response` event.");
1080
+ }
1081
+ if (tokenBuffer) {
1082
+ return {
1083
+ content: tokenBuffer,
1084
+ contentSource: "tokens",
1085
+ sessionId: mintedSessionId ?? metadataSessionId ?? params.sessionId,
1086
+ usage: metricsChunk?.usage,
1087
+ model: metricsChunk?.model,
1088
+ iterations: metricsChunk?.iterations,
1089
+ executionTimeMs: metricsChunk?.executionTimeMs,
1090
+ raw
1091
+ };
1092
+ }
1093
+ throw new ZihinAgentError("protocol", "Stream ended without a `response` event.");
1094
+ }
1095
+ return {
1096
+ content: responseChunk.content,
1097
+ contentSource: "response",
1098
+ sessionId: mintedSessionId ?? responseChunk.sessionId ?? metadataSessionId ?? params.sessionId,
1099
+ model: metricsChunk?.model,
1100
+ usage: metricsChunk?.usage,
1101
+ sources: responseChunk.sources,
1102
+ blocks: responseChunk.blocks,
1103
+ iterations: metricsChunk?.iterations,
1104
+ executionTimeMs: metricsChunk?.executionTimeMs,
1105
+ raw
1106
+ };
1107
+ }
1108
+
1109
+ // src/tasks.ts
1110
+ var MAX_TASK_PAYLOAD_BYTES = 64 * 1024;
1111
+ var SCHEDULE_PAST_TOLERANCE_MS = 6e4;
1112
+ function envelope(json) {
1113
+ const j = rec(json);
1114
+ return { data: rec(j.data ?? j), meta: rec(j.meta) };
1115
+ }
1116
+ function toIso(value, label) {
1117
+ if (value === void 0 || value === null || value === "") return void 0;
1118
+ const d = value instanceof Date ? value : new Date(value);
1119
+ if (Number.isNaN(d.getTime())) {
1120
+ throw new ZihinAgentError("config", `${label} must be a valid ISO 8601 date (got ${JSON.stringify(value)}).`);
1121
+ }
1122
+ return d.toISOString();
1123
+ }
1124
+ function mapTask(raw) {
1125
+ const t = rec(raw);
1126
+ const status = str(t.status);
1127
+ return {
1128
+ id: str(t.id) ?? "",
1129
+ agentId: str(t.agent_id),
1130
+ title: str(t.title),
1131
+ instruction: str(t.instruction),
1132
+ status: status ?? "unknown",
1133
+ // Mesma regra do Caminho B, e de propósito a MESMA função: status desconhecido conta como
1134
+ // terminal (o polling para em vez de girar), status ausente não (não lemos o envelope).
1135
+ terminal: status === void 0 ? false : isTerminalStatus(status),
1136
+ scheduledFor: str(t.scheduled_for),
1137
+ deadlineAt: str(t.deadline_at),
1138
+ parentTaskId: str(t.parent_task_id),
1139
+ rootTaskId: str(t.root_task_id),
1140
+ sessionId: str(t.session_id),
1141
+ createdAt: str(t.created_at),
1142
+ updatedAt: str(t.updated_at),
1143
+ raw
1144
+ };
1145
+ }
1146
+ function mapExecution(raw) {
1147
+ const e = rec(raw);
1148
+ return {
1149
+ id: str(e.id) ?? "",
1150
+ status: str(e.status) ?? "unknown",
1151
+ exitReason: str(e.exit_reason),
1152
+ modelUsed: str(e.model_used),
1153
+ totalCostUsd: num(e.total_cost_usd),
1154
+ executionTimeMs: num(e.execution_time_ms),
1155
+ iterations: num(e.iterations),
1156
+ startedAt: str(e.started_at),
1157
+ finishedAt: str(e.finished_at),
1158
+ raw
1159
+ };
1160
+ }
1161
+ async function delegateTask(cfg, params) {
1162
+ if (!params.agentId) throw new ZihinAgentError("config", "agentId is required.");
1163
+ if (!params.instruction) throw new ZihinAgentError("config", "instruction is required.");
1164
+ const scheduledFor = toIso(params.scheduledFor, "scheduledFor");
1165
+ const deadlineAt = toIso(params.deadlineAt, "deadlineAt");
1166
+ if (scheduledFor && Date.parse(scheduledFor) < Date.now() - SCHEDULE_PAST_TOLERANCE_MS) {
1167
+ throw new ZihinAgentError("config", "scheduledFor is in the past.");
1168
+ }
1169
+ if (deadlineAt && Date.parse(deadlineAt) <= Date.now() - SCHEDULE_PAST_TOLERANCE_MS) {
1170
+ throw new ZihinAgentError("config", "deadlineAt must be in the future.");
1171
+ }
1172
+ if (scheduledFor && deadlineAt && Date.parse(deadlineAt) <= Date.parse(scheduledFor)) {
1173
+ throw new ZihinAgentError("config", "deadlineAt must be after scheduledFor.");
1174
+ }
1175
+ if (params.payload !== void 0) {
1176
+ if (typeof params.payload !== "object" || params.payload === null || Array.isArray(params.payload)) {
1177
+ throw new ZihinAgentError("config", "payload must be an object.");
1178
+ }
1179
+ const bytes = toJsonBody(params.payload, "payload").length;
1180
+ if (bytes > MAX_TASK_PAYLOAD_BYTES) {
1181
+ throw new ZihinAgentError(
1182
+ "config",
1183
+ `payload exceeds ${MAX_TASK_PAYLOAD_BYTES / 1024}KB (got ${Math.ceil(bytes / 1024)}KB).`
1184
+ );
1185
+ }
1186
+ }
1187
+ const body = {
1188
+ agent_id: params.agentId,
1189
+ instruction: params.instruction
1190
+ };
1191
+ if (params.title !== void 0) body.title = params.title;
1192
+ if (params.payload !== void 0) body.payload = params.payload;
1193
+ if (scheduledFor) body.scheduled_for = scheduledFor;
1194
+ if (deadlineAt) body.deadline_at = deadlineAt;
1195
+ if (params.retryPolicy !== void 0) body.retry_policy = params.retryPolicy;
1196
+ if (params.notify !== void 0) body.notify = params.notify;
1197
+ if (params.parentTaskId) body.parent_task_id = params.parentTaskId;
1198
+ const json = await requestJson(cfg, {
1199
+ method: "POST",
1200
+ path: "/api/v1/tasks",
1201
+ body,
1202
+ // Criar uma task E dispará-la é efeito colateral: nunca repetir em erro de rede.
1203
+ policy: "side-effecting",
1204
+ signal: params.signal
1205
+ });
1206
+ const { data } = envelope(json);
1207
+ const task = mapTask(data.task ?? data);
1208
+ if (!task.id) {
1209
+ throw new ZihinAgentError("protocol", "delegateTask: response has no task id.");
1210
+ }
1211
+ return task;
1212
+ }
1213
+ async function listTasks(cfg, params = {}) {
1214
+ const qs = new URLSearchParams();
1215
+ if (params.live) qs.set("live", "true");
1216
+ if (params.status) qs.set("status", params.status);
1217
+ if (params.agentId) qs.set("agent_id", params.agentId);
1218
+ if (params.rootTaskId) qs.set("root_task_id", params.rootTaskId);
1219
+ if (params.limit !== void 0) qs.set("limit", String(params.limit));
1220
+ if (params.offset !== void 0) qs.set("offset", String(params.offset));
1221
+ const query = qs.toString();
1222
+ const json = await requestJson(cfg, {
1223
+ method: "GET",
1224
+ path: `/api/v1/tasks${query ? `?${query}` : ""}`,
1225
+ policy: "idempotent",
1226
+ signal: params.signal
1227
+ });
1228
+ const { data, meta } = envelope(json);
1229
+ const items = Array.isArray(data.items) ? data.items : [];
1230
+ return {
1231
+ items: items.map(mapTask),
1232
+ // ⚠️ `total` vive em `meta`, não no topo — o envelope novo aninhou os campos de paginação.
1233
+ total: num(meta.total),
1234
+ raw: json
1235
+ };
1236
+ }
1237
+ async function getTask(cfg, params) {
1238
+ if (!params.taskId) throw new ZihinAgentError("config", "taskId is required.");
1239
+ const json = await requestJson(cfg, {
1240
+ method: "GET",
1241
+ path: `/api/v1/tasks/${encodeURIComponent(params.taskId)}`,
1242
+ policy: "idempotent",
1243
+ signal: params.signal
1244
+ });
1245
+ const { data } = envelope(json);
1246
+ const taskRaw = data.task ?? (str(rec(data).id) ? data : void 0);
1247
+ if (!taskRaw || !str(rec(taskRaw).id)) {
1248
+ throw new ZihinAgentError("protocol", "getTask: response has no task.");
1249
+ }
1250
+ const executions = Array.isArray(data.executions) ? data.executions : [];
1251
+ const children = Array.isArray(data.children) ? data.children : [];
1252
+ return {
1253
+ task: mapTask(taskRaw),
1254
+ executions: executions.map(mapExecution),
1255
+ children: children.map(mapTask),
1256
+ approvals: Array.isArray(data.approvals) ? data.approvals : [],
1257
+ raw: json
1258
+ };
1259
+ }
1260
+ async function cancelTask(cfg, params) {
1261
+ if (!params.taskId) throw new ZihinAgentError("config", "taskId is required.");
1262
+ const json = await requestJson(cfg, {
1263
+ method: "POST",
1264
+ path: `/api/v1/tasks/${encodeURIComponent(params.taskId)}/cancel`,
1265
+ body: params.reason ? { reason: params.reason } : {},
1266
+ // Cancelar é side-effecting; repetir num socket caído bateria em 409 e confundiria o chamador.
1267
+ policy: "side-effecting",
1268
+ signal: params.signal
1269
+ });
1270
+ const { data } = envelope(json);
1271
+ const taskRaw = data.task ?? (str(rec(data).id) ? data : void 0);
1272
+ if (!taskRaw || !str(rec(taskRaw).id)) {
1273
+ throw new ZihinAgentError("protocol", "cancelTask: response has no task.");
1274
+ }
1275
+ return {
1276
+ task: mapTask(taskRaw),
1277
+ aborted: data.aborted === true,
1278
+ raw: json
1279
+ };
1280
+ }
1281
+
1282
+ // src/triggers.ts
1283
+ function queryExtractionBody(extraction, queryField) {
1284
+ if (!extraction) return { mode: "field", field: queryField ?? "message" };
1285
+ switch (extraction.mode) {
1286
+ case "field":
1287
+ if (!extraction.field) {
1288
+ throw new ZihinAgentError("config", 'queryExtraction.field is required for mode "field".');
1289
+ }
1290
+ return { mode: "field", field: extraction.field };
1291
+ case "template":
1292
+ if (!extraction.template) {
1293
+ throw new ZihinAgentError("config", 'queryExtraction.template is required for mode "template".');
1294
+ }
1295
+ return { mode: "template", template: extraction.template };
1296
+ case "full_body":
1297
+ return { mode: "full_body" };
1298
+ default:
1299
+ throw new ZihinAgentError("config", "Unknown queryExtraction.mode (use field|template|full_body).");
1300
+ }
1301
+ }
1302
+ async function createTrigger(cfg, params) {
1303
+ if (!params.agentId) throw new ZihinAgentError("config", "agentId is required.");
1304
+ if (!params.name) throw new ZihinAgentError("config", "name is required.");
1305
+ const mode = params.mode ?? "async";
1306
+ if (mode === "async" && !params.callbackUrl) {
1307
+ throw new ZihinAgentError("config", "callbackUrl is required for async triggers.");
1308
+ }
1309
+ if (params.callbackUrl) {
1310
+ const cb = validateBaseUrl(params.callbackUrl, { allowCustomDomains: true });
1311
+ if (!cb.valid) {
1312
+ throw new ZihinAgentError("config", `Invalid callbackUrl: ${cb.error}`);
1313
+ }
1314
+ }
1315
+ if (params.responseTransform && !params.callbackUrl) {
1316
+ throw new ZihinAgentError(
1317
+ "config",
1318
+ "responseTransform requires callbackUrl: the backend only reads it from trigger_config.execution.callback.response_transform, and a callback without a url is rejected."
1319
+ );
1320
+ }
1321
+ const execution = { mode };
1322
+ if (params.callbackUrl) {
1323
+ execution.callback = {
1324
+ url: params.callbackUrl,
1325
+ method: params.callbackMethod ?? "POST",
1326
+ ...params.responseTransform ? { response_transform: { mode: params.responseTransform } } : {}
1327
+ };
1328
+ }
1329
+ const body = {
1330
+ agent_id: params.agentId,
1331
+ name: params.name,
1332
+ trigger_type: "webhook",
1333
+ trigger_config: {
1334
+ query_extraction: queryExtractionBody(params.queryExtraction, params.queryField),
1335
+ auth: { type: params.dispatchAuth ?? "api_key" },
1336
+ execution
1337
+ }
1338
+ };
1339
+ const json = rec(
1340
+ await requestJson(cfg, { method: "POST", path: "/api/triggers", body, policy: "side-effecting", signal: params.signal })
1341
+ );
1342
+ const data = rec(json.data);
1343
+ const triggerId = str(data.id) ?? str(json.id);
1344
+ const webhookUrl = str(json.webhook_url) ?? (triggerId ? `${cfg.baseUrl}/api/triggers/webhook/${triggerId}` : void 0);
1345
+ if (!triggerId || !webhookUrl) {
1346
+ throw new ZihinAgentError("protocol", "createTrigger: missing trigger id / webhook url in response.");
1347
+ }
1348
+ return { triggerId, webhookUrl };
1349
+ }
1350
+ async function dispatchAgent(cfg, params) {
1351
+ if (!params.triggerId) throw new ZihinAgentError("config", "triggerId is required.");
1352
+ const res = await requestBuffered({
1353
+ method: "POST",
1354
+ url: `${cfg.baseUrl}/api/triggers/webhook/${encodeURIComponent(params.triggerId)}`,
1355
+ headers: buildHeaders(cfg.apiKey),
1356
+ body: toJsonBody(params.payload ?? {}, "payload"),
1357
+ timeoutMs: resolveTimeout(cfg, "dispatch"),
1358
+ maxRetries: cfg.maxRetries,
1359
+ policy: "side-effecting",
1360
+ // S1: nunca duplica disparo por erro de rede
1361
+ callerSignal: params.signal,
1362
+ fetchImpl: cfg.fetchImpl,
1363
+ onRetry: cfg.onRetry
1364
+ });
1365
+ if (!res.ok) {
1366
+ throw errorFromResponse(res.status, parseBody(res.text), { endpoint: res.endpoint, attempts: res.attempts });
1367
+ }
1368
+ const json = parseBody(res.text);
1369
+ const j = rec(json);
1370
+ if (j.success === false) {
1371
+ const detail = sanitizeApiError(str(j.error) ?? "").message;
1372
+ throw new ZihinAgentError("server", detail || "The agent run failed.", {
1373
+ status: res.status,
1374
+ endpoint: res.endpoint,
1375
+ attempts: res.attempts,
1376
+ executionId: str(j.execution_id),
1377
+ // Não é transitório: o turno rodou e falhou. Repetir gastaria outra execução.
1378
+ retryable: false
1379
+ });
1380
+ }
1381
+ return {
1382
+ accepted: true,
1383
+ triggerId: str(j.trigger_id) ?? params.triggerId,
1384
+ agentId: str(j.agent_id),
1385
+ executionId: str(j.execution_id),
1386
+ agentResponse: str(j.agent_response),
1387
+ usage: parseUsage(j.usage),
1388
+ iterations: num(j.iterations),
1389
+ latencyMs: num(j.latency_ms),
1390
+ // Qual texto o BE extraiu do payload — é o campo que denuncia um `query_extraction` mal
1391
+ // configurado (mensagem vazia, ou o payload inteiro serializado virando a pergunta).
1392
+ queryExtracted: str(j.query_extracted),
1393
+ raw: json
1394
+ };
1395
+ }
1396
+ function webhookUrlFor(cfg, t) {
1397
+ if (str(t.webhook_url)) return str(t.webhook_url);
1398
+ const id = str(t.id);
1399
+ return t.trigger_type === "webhook" && id ? `${cfg.baseUrl}/api/triggers/webhook/${id}` : void 0;
1400
+ }
1401
+ function mapTriggerInfo(cfg, raw) {
1402
+ const t = rec(raw);
1403
+ return {
1404
+ id: str(t.id) ?? "",
1405
+ agentId: str(t.agent_id),
1406
+ name: str(t.name),
1407
+ description: str(t.description),
1408
+ triggerType: str(t.trigger_type),
1409
+ enabled: typeof t.enabled === "boolean" ? t.enabled : void 0,
1410
+ triggerCount: num(t.trigger_count),
1411
+ lastError: str(t.last_error),
1412
+ webhookUrl: webhookUrlFor(cfg, t)
1413
+ };
1414
+ }
1415
+ async function listTriggers(cfg, params = {}) {
1416
+ const qs = new URLSearchParams();
1417
+ if (params.agentId) qs.set("agentId", params.agentId);
1418
+ if (params.triggerType) qs.set("triggerType", params.triggerType);
1419
+ if (params.enabledOnly) qs.set("enabledOnly", "true");
1420
+ const query = qs.toString();
1421
+ const path = `/api/triggers${query ? `?${query}` : ""}`;
1422
+ const json = await requestJson(cfg, { method: "GET", path, policy: "idempotent", signal: params.signal });
1423
+ const list = Array.isArray(json) ? json : Array.isArray(rec(json).data) ? rec(json).data : [];
1424
+ return list.map((t) => mapTriggerInfo(cfg, t));
1425
+ }
1426
+ async function getTrigger(cfg, params) {
1427
+ if (!params.triggerId) throw new ZihinAgentError("config", "triggerId is required.");
1428
+ const json = await requestJson(cfg, {
1429
+ method: "GET",
1430
+ path: `/api/triggers/${encodeURIComponent(params.triggerId)}`,
1431
+ policy: "idempotent",
1432
+ signal: params.signal
1433
+ });
1434
+ return mapTriggerInfo(cfg, rec(json).data ?? json);
1435
+ }
1436
+ async function updateTrigger(cfg, params) {
1437
+ if (!params.triggerId) throw new ZihinAgentError("config", "triggerId is required.");
1438
+ if (params.callbackUrl) {
1439
+ const cb = validateBaseUrl(params.callbackUrl, { allowCustomDomains: true });
1440
+ if (!cb.valid) {
1441
+ throw new ZihinAgentError("config", `Invalid callbackUrl: ${cb.error}`);
1442
+ }
1443
+ }
1444
+ const body = {};
1445
+ if (params.name !== void 0) body.name = params.name;
1446
+ if (params.description !== void 0) body.description = params.description;
1447
+ if (params.enabled !== void 0) body.enabled = params.enabled;
1448
+ const touchesConfig = params.queryExtraction !== void 0 || params.callbackUrl !== void 0 || params.callbackMethod !== void 0 || params.responseTransform !== void 0 || params.mode !== void 0;
1449
+ if (touchesConfig) {
1450
+ const path = `/api/triggers/${encodeURIComponent(params.triggerId)}`;
1451
+ const currentJson = rec(
1452
+ await requestJson(cfg, { method: "GET", path, policy: "idempotent", signal: params.signal })
1453
+ );
1454
+ const current = rec(currentJson.data ?? currentJson);
1455
+ if (!current.trigger_config || typeof current.trigger_config !== "object" || Object.keys(rec(current.trigger_config)).length === 0) {
1456
+ throw new ZihinAgentError(
1457
+ "protocol",
1458
+ "updateTrigger: current trigger_config unavailable \u2014 refusing to overwrite it partially."
1459
+ );
1460
+ }
1461
+ const config = rec(current.trigger_config);
1462
+ const merged = { ...config };
1463
+ if (params.queryExtraction !== void 0) {
1464
+ merged.query_extraction = queryExtractionBody(params.queryExtraction, void 0);
1465
+ }
1466
+ const touchesCallback = params.callbackUrl !== void 0 || params.callbackMethod !== void 0 || params.responseTransform !== void 0;
1467
+ if (params.mode !== void 0 || touchesCallback) {
1468
+ const execution = rec(config.execution);
1469
+ const callback = rec(execution.callback);
1470
+ const newExecution = { ...execution };
1471
+ if (params.mode !== void 0) newExecution.mode = params.mode;
1472
+ if (touchesCallback) {
1473
+ const url = params.callbackUrl ?? str(callback.url);
1474
+ if (!url) {
1475
+ throw new ZihinAgentError(
1476
+ "config",
1477
+ params.responseTransform ? "responseTransform requires a callback: the backend only reads it from trigger_config.execution.callback.response_transform, and a callback without a url is rejected." : "callbackUrl is required: this trigger has no callback configured yet."
1478
+ );
1479
+ }
1480
+ newExecution.callback = {
1481
+ // Preserva o callback atual: trocar só a URL não pode apagar auth/headers/
1482
+ // body_template/content_type/response_transform. Antes este objeto era
1483
+ // SUBSTITUÍDO por { url, method } — a mesma perda silenciosa que o merge de
1484
+ // trigger_config acima existe para evitar, um nível mais fundo.
1485
+ ...callback,
1486
+ url,
1487
+ method: params.callbackMethod ?? str(callback.method) ?? "POST",
1488
+ ...params.responseTransform ? {
1489
+ response_transform: {
1490
+ ...rec(callback.response_transform),
1491
+ mode: params.responseTransform
1492
+ }
1493
+ } : {}
1494
+ };
1495
+ }
1496
+ if (newExecution.mode === "async" && !rec(newExecution.callback).url) {
1497
+ throw new ZihinAgentError("config", "callbackUrl is required for async triggers.");
1498
+ }
1499
+ merged.execution = newExecution;
1500
+ }
1501
+ body.trigger_config = merged;
1502
+ }
1503
+ if (Object.keys(body).length === 0) {
1504
+ throw new ZihinAgentError("config", "updateTrigger: no fields to update.");
1505
+ }
1506
+ const json = await requestJson(cfg, {
1507
+ method: "PATCH",
1508
+ path: `/api/triggers/${encodeURIComponent(params.triggerId)}`,
1509
+ body,
1510
+ // PATCH aqui é replay-safe: reaplicar os mesmos valores produz o mesmo estado.
1511
+ policy: "idempotent",
1512
+ signal: params.signal
1513
+ });
1514
+ return mapTriggerInfo(cfg, rec(json).data ?? json);
1515
+ }
1516
+ async function deleteTrigger(cfg, params) {
1517
+ if (!params.triggerId) throw new ZihinAgentError("config", "triggerId is required.");
1518
+ const res = await requestBuffered({
1519
+ method: "DELETE",
1520
+ url: `${cfg.baseUrl}/api/triggers/${encodeURIComponent(params.triggerId)}`,
1521
+ headers: buildHeaders(cfg.apiKey),
1522
+ timeoutMs: resolveTimeout(cfg, "short"),
1523
+ maxRetries: cfg.maxRetries,
1524
+ policy: "idempotent",
1525
+ // DELETE é idempotente
1526
+ callerSignal: params.signal,
1527
+ fetchImpl: cfg.fetchImpl,
1528
+ onRetry: cfg.onRetry
1529
+ });
1530
+ if (!res.ok) {
1531
+ throw errorFromResponse(res.status, parseBody(res.text), { endpoint: res.endpoint, attempts: res.attempts });
1532
+ }
1533
+ const j = rec(parseBody(res.text));
1534
+ return { deleted: j.deleted === true || res.status === 200 };
1535
+ }
1536
+ async function findOrCreateTrigger(cfg, params) {
1537
+ if (!params.agentId) throw new ZihinAgentError("config", "agentId is required.");
1538
+ if (!params.name) throw new ZihinAgentError("config", "name is required.");
1539
+ const existing = await listTriggers(cfg, { agentId: params.agentId, signal: params.signal });
1540
+ const match = existing.find((t) => t.name === params.name);
1541
+ if (match && match.id) {
1542
+ const webhookUrl = match.webhookUrl ?? `${cfg.baseUrl}/api/triggers/webhook/${match.id}`;
1543
+ return { triggerId: match.id, webhookUrl, created: false };
1544
+ }
1545
+ const ref = await createTrigger(cfg, params);
1546
+ return { ...ref, created: true };
1547
+ }
1548
+ async function getExecution(cfg, params) {
1549
+ if (!params.triggerId || !params.executionId) {
1550
+ throw new ZihinAgentError("config", "triggerId and executionId are required.");
1551
+ }
1552
+ const path = `/api/triggers/${encodeURIComponent(params.triggerId)}/executions/${encodeURIComponent(
1553
+ params.executionId
1554
+ )}`;
1555
+ const json = rec(await requestJson(cfg, { method: "GET", path, policy: "idempotent", signal: params.signal }));
1556
+ const data = rec(json.data);
1557
+ const exec = rec(data.execution ?? json.execution);
1558
+ const metrics = rec(data.metrics ?? json.metrics);
1559
+ const rawStatus = str(exec.status);
1560
+ return {
1561
+ executionId: str(exec.id) ?? params.executionId,
1562
+ status: rawStatus ?? "unknown",
1563
+ terminal: rawStatus === void 0 ? false : isTerminalStatus(rawStatus),
1564
+ startedAt: str(exec.started_at),
1565
+ finishedAt: str(exec.finished_at),
1566
+ executionTimeMs: num(exec.execution_time_ms),
1567
+ usage: parseUsage(metrics.usage ?? metrics) ?? parseUsage(exec.usage),
1568
+ result: str(exec.agent_response) ?? str(exec.result),
1569
+ raw: json
1570
+ };
1571
+ }
1572
+
1573
+ // src/client.ts
1574
+ var ZihinAgentClient = class {
1575
+ constructor(config) {
1576
+ this.cfg = resolveConfig(config);
1577
+ }
1578
+ /** Base URL efetiva (após validação SSRF). */
1579
+ get baseUrl() {
1580
+ return this.cfg.baseUrl;
1581
+ }
1582
+ /** Notifica `config.onError` (falha definitiva); o callback nunca derruba o fluxo. */
1583
+ report(err) {
1584
+ if (this.cfg.onError && err instanceof ZihinAgentError) {
1585
+ try {
1586
+ this.cfg.onError(err);
1587
+ } catch {
1588
+ }
1589
+ }
1590
+ }
1591
+ guard(p) {
1592
+ return p.catch((err) => {
1593
+ this.report(err);
1594
+ throw err;
1595
+ });
1596
+ }
1597
+ // ---- Caminho A: direto (SSE) ----
1598
+ /** Invoca o agente e retorna o resultado final consolidado (response + metrics). */
1599
+ invokeAgent(params) {
1600
+ return this.guard(invokeAgent(this.cfg, params));
1601
+ }
1602
+ /** Emite deltas ao vivo do agente (async iterator). */
1603
+ async *streamAgent(params) {
1604
+ try {
1605
+ yield* streamAgent(this.cfg, params);
1606
+ } catch (err) {
1607
+ this.report(err);
1608
+ throw err;
1609
+ }
1610
+ }
1611
+ // ---- Caminho B: triggers (async/callback) ----
1612
+ /** Provisiona (1x) um trigger de webhook ligado a um agente. */
1613
+ createTrigger(params) {
1614
+ return this.guard(createTrigger(this.cfg, params));
1615
+ }
1616
+ /** Reaproveita (por agente+nome) ou cria um trigger — evita duplicação. */
1617
+ findOrCreateTrigger(params) {
1618
+ return this.guard(findOrCreateTrigger(this.cfg, params));
1619
+ }
1620
+ /** Lista os triggers do tenant (filtros opcionais). */
1621
+ listTriggers(params = {}) {
1622
+ return this.guard(listTriggers(this.cfg, params));
1623
+ }
1624
+ /** Obtém um trigger pelo id (inclui webhookUrl). */
1625
+ getTrigger(params) {
1626
+ return this.guard(getTrigger(this.cfg, params));
1627
+ }
1628
+ /** Atualiza um trigger (name/description/enabled/extração/callback) via PATCH. */
1629
+ updateTrigger(params) {
1630
+ return this.guard(updateTrigger(this.cfg, params));
1631
+ }
1632
+ /** Remove um trigger (requer key admin/owner). */
1633
+ deleteTrigger(params) {
1634
+ return this.guard(deleteTrigger(this.cfg, params));
1635
+ }
1636
+ /** Dispara um trigger: 202 (async) ou JSON imediato (sync). */
1637
+ dispatchAgent(params) {
1638
+ return this.guard(dispatchAgent(this.cfg, params));
1639
+ }
1640
+ /** Consulta o resultado de uma execução disparada por trigger (polling). */
1641
+ getExecution(params) {
1642
+ return this.guard(getExecution(this.cfg, params));
1643
+ }
1644
+ // ---- Caminho C: tasks (delegação assíncrona durável) ----
1645
+ /**
1646
+ * Delega uma tarefa a um agente (requer key owner/admin). Sem `scheduledFor`, o BE dispara na
1647
+ * hora fora do request e devolve só o handle — acompanhe com `getTask`.
1648
+ */
1649
+ delegateTask(params) {
1650
+ return this.guard(delegateTask(this.cfg, params));
1651
+ }
1652
+ /** Board de tasks do tenant (requer key owner/admin/editor). */
1653
+ listTasks(params = {}) {
1654
+ return this.guard(listTasks(this.cfg, params));
1655
+ }
1656
+ /** Dossiê de uma task: task + execuções + filhos + aprovações. */
1657
+ getTask(params) {
1658
+ return this.guard(getTask(this.cfg, params));
1659
+ }
1660
+ /** Cancela uma task (requer key owner/admin); derruba o turno em voo, se houver. */
1661
+ cancelTask(params) {
1662
+ return this.guard(cancelTask(this.cfg, params));
1663
+ }
1664
+ // ---- Auxiliar ----
1665
+ /** Lista os agentes do tenant (dropdown). */
1666
+ listAgents(opts = {}) {
1667
+ return this.guard(listAgents(this.cfg, opts));
1668
+ }
1669
+ };
1670
+ function createClient(config) {
1671
+ return new ZihinAgentClient(config);
1672
+ }
1673
+
1674
+ // src/signing.ts
1675
+ function loadNodeCrypto() {
1676
+ const proc = globalThis.process;
1677
+ if (typeof proc?.getBuiltinModule === "function") {
1678
+ return proc.getBuiltinModule("node:crypto");
1679
+ }
1680
+ try {
1681
+ return require("crypto");
1682
+ } catch {
1683
+ throw new ZihinAgentError(
1684
+ "config",
1685
+ "signRequest requires Node.js crypto, unavailable in this runtime (the ESM build needs Node >=20.16)."
1686
+ );
1687
+ }
1688
+ }
1689
+ function signRequest(payload, secretKey, timestamp) {
1690
+ const { createHmac, randomBytes } = loadNodeCrypto();
1691
+ const ts = timestamp || Date.now();
1692
+ const nonce = randomBytes(16).toString("hex");
1693
+ const message = `${ts}.${nonce}.${payload}`;
1694
+ const signature = createHmac("sha256", secretKey).update(message).digest("hex");
1695
+ return {
1696
+ "X-Zihin-Signature": `sha256=${signature}`,
1697
+ "X-Zihin-Timestamp": ts.toString(),
1698
+ "X-Zihin-Nonce": nonce
1699
+ };
1700
+ }
1701
+ // Annotate the CommonJS export names for ESM import in node:
1702
+ 0 && (module.exports = {
1703
+ DEADLINE_GRACE_MS,
1704
+ DEFAULT_LIMITS,
1705
+ MAX_TASK_PAYLOAD_BYTES,
1706
+ OPERATION_TIMEOUT_MS,
1707
+ ZihinAgentClient,
1708
+ ZihinAgentError,
1709
+ createClient,
1710
+ isCommercialBlock,
1711
+ sanitizeApiError,
1712
+ signRequest,
1713
+ validateApiKeyFormat,
1714
+ validateBaseUrl,
1715
+ validatePromptSize
1716
+ });