@zihin/agent-client 0.2.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/README.md +69 -2
- package/dist/index.d.mts +283 -4
- package/dist/index.d.ts +283 -4
- package/dist/index.js +472 -60
- package/dist/index.mjs +468 -60
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -20,10 +20,14 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
20
20
|
// src/index.ts
|
|
21
21
|
var index_exports = {};
|
|
22
22
|
__export(index_exports, {
|
|
23
|
+
DEADLINE_GRACE_MS: () => DEADLINE_GRACE_MS,
|
|
23
24
|
DEFAULT_LIMITS: () => DEFAULT_LIMITS,
|
|
25
|
+
MAX_TASK_PAYLOAD_BYTES: () => MAX_TASK_PAYLOAD_BYTES,
|
|
26
|
+
OPERATION_TIMEOUT_MS: () => OPERATION_TIMEOUT_MS,
|
|
24
27
|
ZihinAgentClient: () => ZihinAgentClient,
|
|
25
28
|
ZihinAgentError: () => ZihinAgentError,
|
|
26
29
|
createClient: () => createClient,
|
|
30
|
+
isCommercialBlock: () => isCommercialBlock,
|
|
27
31
|
sanitizeApiError: () => sanitizeApiError,
|
|
28
32
|
signRequest: () => signRequest,
|
|
29
33
|
validateApiKeyFormat: () => validateApiKeyFormat,
|
|
@@ -56,6 +60,21 @@ function parseUsage(usageRaw) {
|
|
|
56
60
|
costUsd: num(u.cost_usd)
|
|
57
61
|
};
|
|
58
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
|
+
}
|
|
59
78
|
|
|
60
79
|
// src/security.ts
|
|
61
80
|
var PRIVATE_IP_PATTERNS = [
|
|
@@ -238,14 +257,19 @@ var SENSITIVE_PATTERNS = [
|
|
|
238
257
|
var ERROR_CODE_MAP = {
|
|
239
258
|
RATE_LIMIT_EXCEEDED: "Rate limit exceeded. Please wait before retrying.",
|
|
240
259
|
UNAUTHORIZED: "Authentication failed. Please check your API credentials.",
|
|
241
|
-
FORBIDDEN: "This API key
|
|
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.",
|
|
242
261
|
INVALID_MODEL: "The specified model is not available.",
|
|
243
262
|
INTERNAL_ERROR: "An internal error occurred. Please try again later.",
|
|
244
263
|
TIMEOUT: "Request timed out. Try increasing the timeout.",
|
|
245
264
|
INVALID_REQUEST: "The request was malformed. Please check your input.",
|
|
246
265
|
INSUFFICIENT_CREDITS: "Insufficient credits. Please check your account balance.",
|
|
247
266
|
CONTEXT_LENGTH_EXCEEDED: "The input exceeds the model context length limit.",
|
|
248
|
-
AGENT_NOT_FOUND: "Agent not found or not active for this tenant."
|
|
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."
|
|
249
273
|
};
|
|
250
274
|
function sanitizeApiError(error) {
|
|
251
275
|
let message = "An unexpected error occurred";
|
|
@@ -258,8 +282,10 @@ function sanitizeApiError(error) {
|
|
|
258
282
|
const codeRaw = nested?.code ?? (typeof err.error === "string" ? err.error : err.code);
|
|
259
283
|
code = typeof codeRaw === "string" ? codeRaw : void 0;
|
|
260
284
|
const rawMessage = nested?.message ?? err.message;
|
|
261
|
-
|
|
262
|
-
|
|
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];
|
|
263
289
|
} else if (typeof rawMessage === "string" && rawMessage) {
|
|
264
290
|
message = rawMessage;
|
|
265
291
|
}
|
|
@@ -308,6 +334,7 @@ var ZihinAgentError = class _ZihinAgentError extends Error {
|
|
|
308
334
|
this.status = opts.status;
|
|
309
335
|
this.code = opts.code;
|
|
310
336
|
this.requestId = opts.requestId;
|
|
337
|
+
this.executionId = opts.executionId;
|
|
311
338
|
this.attempts = opts.attempts;
|
|
312
339
|
this.endpoint = opts.endpoint;
|
|
313
340
|
this.retryable = opts.retryable ?? (kind === "rate_limit" || kind === "server" || kind === "network");
|
|
@@ -319,25 +346,65 @@ function buildMessage(base, opts) {
|
|
|
319
346
|
if (opts.endpoint) s += ` [${opts.endpoint}]`;
|
|
320
347
|
if (opts.attempts && opts.attempts > 1) s += ` (after ${opts.attempts} attempts)`;
|
|
321
348
|
if (opts.requestId) s += ` (request_id: ${opts.requestId})`;
|
|
349
|
+
if (opts.executionId) s += ` (execution_id: ${opts.executionId})`;
|
|
322
350
|
return s;
|
|
323
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
|
+
}
|
|
324
370
|
function extractRequestId(body) {
|
|
325
371
|
const b = rec(body);
|
|
326
|
-
return str(b.request_id) ?? str(b.requestId) ?? str(rec(b.error).request_id);
|
|
372
|
+
return str(b.request_id) ?? str(b.requestId) ?? str(rec(b.error).request_id) ?? str(rec(b.meta).request_id);
|
|
327
373
|
}
|
|
328
374
|
function errorFromResponse(status, body, meta = {}) {
|
|
329
375
|
const sanitized = sanitizeApiError(body);
|
|
330
376
|
const message = sanitized.message;
|
|
331
377
|
const code = sanitized.code;
|
|
332
|
-
const base = {
|
|
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
|
+
}
|
|
333
392
|
if (status === 401) return new ZihinAgentError("auth", message || "Authentication failed.", base);
|
|
334
393
|
if (status === 403)
|
|
335
394
|
return new ZihinAgentError(
|
|
336
395
|
"auth",
|
|
337
|
-
|
|
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.",
|
|
338
400
|
base
|
|
339
401
|
);
|
|
340
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
|
+
});
|
|
341
408
|
if (status === 429)
|
|
342
409
|
return new ZihinAgentError("rate_limit", message || "Rate limit exceeded.", { ...base, retryable: true });
|
|
343
410
|
if (status >= 500)
|
|
@@ -367,7 +434,25 @@ function errorFromThrow(err, timeoutMs, meta = {}) {
|
|
|
367
434
|
|
|
368
435
|
// src/http.ts
|
|
369
436
|
var DEFAULT_BASE_URL = "https://llm.zihin.ai";
|
|
370
|
-
var
|
|
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
|
+
}
|
|
371
456
|
var DEFAULT_MAX_RETRIES = 2;
|
|
372
457
|
var DEFAULT_MAX_EVENT_BYTES = 1 * 1024 * 1024;
|
|
373
458
|
var DEFAULT_MAX_TOTAL_BYTES = 64 * 1024 * 1024;
|
|
@@ -400,7 +485,7 @@ function resolveConfig(config) {
|
|
|
400
485
|
return {
|
|
401
486
|
apiKey: config.apiKey.trim(),
|
|
402
487
|
baseUrl: urlCheck.sanitizedUrl || DEFAULT_BASE_URL,
|
|
403
|
-
timeoutMs: config.timeoutMs
|
|
488
|
+
timeoutMs: config.timeoutMs === void 0 || config.timeoutMs === null ? void 0 : normalizeTimeoutMs(config.timeoutMs, "timeoutMs"),
|
|
404
489
|
maxRetries: config.maxRetries ?? DEFAULT_MAX_RETRIES,
|
|
405
490
|
maxStreamEventBytes: config.maxStreamEventBytes ?? DEFAULT_MAX_EVENT_BYTES,
|
|
406
491
|
maxStreamTotalBytes: config.maxStreamTotalBytes ?? DEFAULT_MAX_TOTAL_BYTES,
|
|
@@ -409,6 +494,9 @@ function resolveConfig(config) {
|
|
|
409
494
|
onError: config.onError
|
|
410
495
|
};
|
|
411
496
|
}
|
|
497
|
+
function resolveTimeout(cfg, operation) {
|
|
498
|
+
return cfg.timeoutMs ?? OPERATION_TIMEOUT_MS[operation];
|
|
499
|
+
}
|
|
412
500
|
function endpointLabel(method, url) {
|
|
413
501
|
try {
|
|
414
502
|
const u = new URL(url);
|
|
@@ -482,12 +570,27 @@ function createDeadline(timeoutMs, callerSignal) {
|
|
|
482
570
|
};
|
|
483
571
|
}
|
|
484
572
|
function shouldRetryStatus(status, policy) {
|
|
485
|
-
if (policy === "idempotent") return status >= 500 || status === 429;
|
|
573
|
+
if (policy === "idempotent") return status >= 500 || status === 429 || status === 408;
|
|
486
574
|
return status === 429 || status === 503;
|
|
487
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
|
+
}
|
|
488
590
|
async function requestBuffered(args) {
|
|
489
591
|
const endpoint = endpointLabel(args.method, args.url);
|
|
490
592
|
const doFetch = resolveFetch(args.fetchImpl);
|
|
593
|
+
const budget = budgetTracker({ ...args, endpoint });
|
|
491
594
|
let lastError = null;
|
|
492
595
|
for (let attempt = 0; attempt <= args.maxRetries; attempt++) {
|
|
493
596
|
const d = createDeadline(args.timeoutMs, args.callerSignal);
|
|
@@ -502,30 +605,39 @@ async function requestBuffered(args) {
|
|
|
502
605
|
cache: "no-store"
|
|
503
606
|
// evita cache stale (ex.: polling em Next.js/edge)
|
|
504
607
|
});
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
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
|
+
}
|
|
510
620
|
}
|
|
511
621
|
d.dispose();
|
|
512
622
|
args.onRetry?.({ endpoint, attempt: attempt + 1, delayMs: wait, reason: "status", status: res.status });
|
|
513
623
|
await delay(wait, args.callerSignal);
|
|
514
624
|
continue;
|
|
515
625
|
}
|
|
516
|
-
const text = await res.text();
|
|
626
|
+
const text = peeked ?? await res.text();
|
|
517
627
|
d.dispose();
|
|
518
628
|
return { status: res.status, ok: res.ok, text, endpoint, attempts: attempt + 1 };
|
|
519
629
|
} catch (err) {
|
|
520
630
|
d.dispose();
|
|
631
|
+
if (err instanceof ZihinAgentError) throw err;
|
|
521
632
|
const meta = { endpoint, attempts: attempt + 1 };
|
|
522
|
-
if (d.timedOut()) throw
|
|
633
|
+
if (d.timedOut()) throw budget.expired(attempt + 1);
|
|
523
634
|
if (d.abortedByCaller())
|
|
524
635
|
throw new ZihinAgentError("aborted", "Request was aborted by the caller.", meta);
|
|
525
|
-
lastError = errorFromThrow(err,
|
|
636
|
+
lastError = errorFromThrow(err, budget.reported, meta);
|
|
526
637
|
const canRetry = args.policy === "idempotent" && lastError.retryable && attempt < args.maxRetries;
|
|
527
638
|
if (!canRetry) throw lastError;
|
|
528
639
|
const wait = backoffMs(attempt);
|
|
640
|
+
if (budget.overheadLeft() <= wait) throw lastError;
|
|
529
641
|
args.onRetry?.({ endpoint, attempt: attempt + 1, delayMs: wait, reason: "network" });
|
|
530
642
|
await delay(wait, args.callerSignal);
|
|
531
643
|
}
|
|
@@ -535,6 +647,7 @@ async function requestBuffered(args) {
|
|
|
535
647
|
async function openStream(args) {
|
|
536
648
|
const endpoint = endpointLabel("POST", args.url);
|
|
537
649
|
const doFetch = resolveFetch(args.fetchImpl);
|
|
650
|
+
const budget = budgetTracker({ ...args, endpoint });
|
|
538
651
|
for (let attempt = 0; attempt <= args.maxRetries; attempt++) {
|
|
539
652
|
const d = createDeadline(args.timeoutMs, args.callerSignal);
|
|
540
653
|
try {
|
|
@@ -546,8 +659,20 @@ async function openStream(args) {
|
|
|
546
659
|
redirect: "error",
|
|
547
660
|
cache: "no-store"
|
|
548
661
|
});
|
|
549
|
-
|
|
550
|
-
|
|
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) {
|
|
551
676
|
try {
|
|
552
677
|
await response.body?.cancel();
|
|
553
678
|
} catch {
|
|
@@ -566,15 +691,51 @@ async function openStream(args) {
|
|
|
566
691
|
return { response, deadline: d, endpoint };
|
|
567
692
|
} catch (err) {
|
|
568
693
|
d.dispose();
|
|
694
|
+
if (err instanceof ZihinAgentError) throw err;
|
|
569
695
|
const meta = { endpoint, attempts: attempt + 1 };
|
|
570
|
-
if (d.timedOut()) throw
|
|
696
|
+
if (d.timedOut()) throw budget.expired(attempt + 1);
|
|
571
697
|
if (d.abortedByCaller())
|
|
572
698
|
throw new ZihinAgentError("aborted", "Request was aborted by the caller.", meta);
|
|
573
|
-
throw errorFromThrow(err,
|
|
699
|
+
throw errorFromThrow(err, budget.reported, meta);
|
|
574
700
|
}
|
|
575
701
|
}
|
|
576
702
|
throw new ZihinAgentError("unknown", "Max retries exceeded.", { endpoint });
|
|
577
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
|
+
}
|
|
578
739
|
function parseBody(text) {
|
|
579
740
|
if (!text) return {};
|
|
580
741
|
try {
|
|
@@ -590,7 +751,7 @@ async function listAgents(cfg, opts = {}) {
|
|
|
590
751
|
method: "GET",
|
|
591
752
|
url: `${cfg.baseUrl}/api/v2/agents`,
|
|
592
753
|
headers: buildHeaders(cfg.apiKey),
|
|
593
|
-
timeoutMs: cfg
|
|
754
|
+
timeoutMs: resolveTimeout(cfg, "short"),
|
|
594
755
|
maxRetries: cfg.maxRetries,
|
|
595
756
|
policy: "idempotent",
|
|
596
757
|
callerSignal: opts.signal,
|
|
@@ -757,6 +918,20 @@ function mapEvent(e) {
|
|
|
757
918
|
return { type: "token", content: str(d.content) ?? "", data: e.data };
|
|
758
919
|
case "warning":
|
|
759
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
|
+
}
|
|
760
935
|
case "response":
|
|
761
936
|
return {
|
|
762
937
|
type: "response",
|
|
@@ -781,7 +956,7 @@ function mapEvent(e) {
|
|
|
781
956
|
return { type: "unknown", event: e.event, data: e.data };
|
|
782
957
|
}
|
|
783
958
|
}
|
|
784
|
-
function buildRequestBody(params) {
|
|
959
|
+
function buildRequestBody(params, normalizedTimeoutMs) {
|
|
785
960
|
const body = {};
|
|
786
961
|
if (params.message !== void 0) body.message = params.message;
|
|
787
962
|
if (params.sessionId) body.session_id = params.sessionId;
|
|
@@ -792,7 +967,7 @@ function buildRequestBody(params) {
|
|
|
792
967
|
const o = {};
|
|
793
968
|
if (params.options.temperature !== void 0) o.temperature = params.options.temperature;
|
|
794
969
|
if (params.options.maxTokens !== void 0) o.max_tokens = params.options.maxTokens;
|
|
795
|
-
if (
|
|
970
|
+
if (normalizedTimeoutMs !== void 0) o.timeout_ms = normalizedTimeoutMs;
|
|
796
971
|
if (params.options.jitMode !== void 0) o.jit_mode = params.options.jitMode;
|
|
797
972
|
if (Object.keys(o).length) body.options = o;
|
|
798
973
|
}
|
|
@@ -823,6 +998,9 @@ function validateParams(params) {
|
|
|
823
998
|
throw new ZihinAgentError("config", sizeCheck.error || "Message too large.");
|
|
824
999
|
}
|
|
825
1000
|
}
|
|
1001
|
+
if (params.options?.timeoutMs !== void 0) {
|
|
1002
|
+
normalizeTimeoutMs(params.options.timeoutMs, "options.timeoutMs");
|
|
1003
|
+
}
|
|
826
1004
|
for (const att of params.attachments ?? []) {
|
|
827
1005
|
const urlCheck = validateBaseUrl(att.url, { allowCustomDomains: true });
|
|
828
1006
|
if (!urlCheck.valid) {
|
|
@@ -832,13 +1010,16 @@ function validateParams(params) {
|
|
|
832
1010
|
}
|
|
833
1011
|
async function* streamAgent(cfg, params) {
|
|
834
1012
|
validateParams(params);
|
|
835
|
-
const
|
|
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;
|
|
836
1016
|
const url = `${cfg.baseUrl}/api/v2/agents/${encodeURIComponent(params.agentId)}/stream`;
|
|
837
1017
|
const { response, deadline, endpoint } = await openStream({
|
|
838
1018
|
url,
|
|
839
1019
|
headers: buildHeaders(cfg.apiKey),
|
|
840
|
-
body:
|
|
1020
|
+
body: toJsonBody(buildRequestBody(params, callerTimeoutMs), "request body"),
|
|
841
1021
|
timeoutMs,
|
|
1022
|
+
reportedTimeoutMs: reportedMs,
|
|
842
1023
|
maxRetries: cfg.maxRetries,
|
|
843
1024
|
callerSignal: params.signal,
|
|
844
1025
|
fetchImpl: cfg.fetchImpl,
|
|
@@ -853,7 +1034,7 @@ async function* streamAgent(cfg, params) {
|
|
|
853
1034
|
}
|
|
854
1035
|
throw errorFromResponse(response.status, parseBody(text), { endpoint });
|
|
855
1036
|
}
|
|
856
|
-
const onAbortError = () => deadline.timedOut() ? new ZihinAgentError("timeout", `Stream timed out after ${
|
|
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 });
|
|
857
1038
|
for await (const e of parseSse(response, {
|
|
858
1039
|
signal: params.signal,
|
|
859
1040
|
onAbortError,
|
|
@@ -872,19 +1053,36 @@ async function invokeAgent(cfg, params) {
|
|
|
872
1053
|
let responseChunk;
|
|
873
1054
|
let metricsChunk;
|
|
874
1055
|
let metadataSessionId;
|
|
1056
|
+
let mintedSessionId;
|
|
1057
|
+
let cleanEnd = false;
|
|
875
1058
|
let tokenBuffer = "";
|
|
876
1059
|
for await (const chunk of streamAgent(cfg, params)) {
|
|
877
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
|
+
}
|
|
878
1070
|
if (chunk.type === "response") responseChunk = chunk;
|
|
879
1071
|
else if (chunk.type === "metrics") metricsChunk = chunk;
|
|
880
1072
|
else if (chunk.type === "token") tokenBuffer += chunk.content;
|
|
881
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;
|
|
882
1076
|
}
|
|
883
1077
|
if (!responseChunk) {
|
|
1078
|
+
if (cleanEnd) {
|
|
1079
|
+
throw new ZihinAgentError("protocol", "Stream ended (done) without a `response` event.");
|
|
1080
|
+
}
|
|
884
1081
|
if (tokenBuffer) {
|
|
885
1082
|
return {
|
|
886
1083
|
content: tokenBuffer,
|
|
887
|
-
|
|
1084
|
+
contentSource: "tokens",
|
|
1085
|
+
sessionId: mintedSessionId ?? metadataSessionId ?? params.sessionId,
|
|
888
1086
|
usage: metricsChunk?.usage,
|
|
889
1087
|
model: metricsChunk?.model,
|
|
890
1088
|
iterations: metricsChunk?.iterations,
|
|
@@ -895,8 +1093,9 @@ async function invokeAgent(cfg, params) {
|
|
|
895
1093
|
throw new ZihinAgentError("protocol", "Stream ended without a `response` event.");
|
|
896
1094
|
}
|
|
897
1095
|
return {
|
|
898
|
-
content: responseChunk.content
|
|
899
|
-
|
|
1096
|
+
content: responseChunk.content,
|
|
1097
|
+
contentSource: "response",
|
|
1098
|
+
sessionId: mintedSessionId ?? responseChunk.sessionId ?? metadataSessionId ?? params.sessionId,
|
|
900
1099
|
model: metricsChunk?.model,
|
|
901
1100
|
usage: metricsChunk?.usage,
|
|
902
1101
|
sources: responseChunk.sources,
|
|
@@ -907,29 +1106,180 @@ async function invokeAgent(cfg, params) {
|
|
|
907
1106
|
};
|
|
908
1107
|
}
|
|
909
1108
|
|
|
910
|
-
// src/
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
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
|
|
923
1205
|
});
|
|
924
|
-
|
|
925
|
-
|
|
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.");
|
|
926
1210
|
}
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
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.");
|
|
931
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
|
+
};
|
|
932
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
|
|
933
1283
|
function queryExtractionBody(extraction, queryField) {
|
|
934
1284
|
if (!extraction) return { mode: "field", field: queryField ?? "message" };
|
|
935
1285
|
switch (extraction.mode) {
|
|
@@ -962,11 +1312,18 @@ async function createTrigger(cfg, params) {
|
|
|
962
1312
|
throw new ZihinAgentError("config", `Invalid callbackUrl: ${cb.error}`);
|
|
963
1313
|
}
|
|
964
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
|
+
}
|
|
965
1321
|
const execution = { mode };
|
|
966
1322
|
if (params.callbackUrl) {
|
|
967
1323
|
execution.callback = {
|
|
968
1324
|
url: params.callbackUrl,
|
|
969
|
-
method: params.callbackMethod ?? "POST"
|
|
1325
|
+
method: params.callbackMethod ?? "POST",
|
|
1326
|
+
...params.responseTransform ? { response_transform: { mode: params.responseTransform } } : {}
|
|
970
1327
|
};
|
|
971
1328
|
}
|
|
972
1329
|
const body = {
|
|
@@ -996,8 +1353,8 @@ async function dispatchAgent(cfg, params) {
|
|
|
996
1353
|
method: "POST",
|
|
997
1354
|
url: `${cfg.baseUrl}/api/triggers/webhook/${encodeURIComponent(params.triggerId)}`,
|
|
998
1355
|
headers: buildHeaders(cfg.apiKey),
|
|
999
|
-
body:
|
|
1000
|
-
timeoutMs: cfg
|
|
1356
|
+
body: toJsonBody(params.payload ?? {}, "payload"),
|
|
1357
|
+
timeoutMs: resolveTimeout(cfg, "dispatch"),
|
|
1001
1358
|
maxRetries: cfg.maxRetries,
|
|
1002
1359
|
policy: "side-effecting",
|
|
1003
1360
|
// S1: nunca duplica disparo por erro de rede
|
|
@@ -1010,12 +1367,29 @@ async function dispatchAgent(cfg, params) {
|
|
|
1010
1367
|
}
|
|
1011
1368
|
const json = parseBody(res.text);
|
|
1012
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
|
+
}
|
|
1013
1381
|
return {
|
|
1014
1382
|
accepted: true,
|
|
1015
1383
|
triggerId: str(j.trigger_id) ?? params.triggerId,
|
|
1384
|
+
agentId: str(j.agent_id),
|
|
1016
1385
|
executionId: str(j.execution_id),
|
|
1017
1386
|
agentResponse: str(j.agent_response),
|
|
1018
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),
|
|
1019
1393
|
raw: json
|
|
1020
1394
|
};
|
|
1021
1395
|
}
|
|
@@ -1071,7 +1445,7 @@ async function updateTrigger(cfg, params) {
|
|
|
1071
1445
|
if (params.name !== void 0) body.name = params.name;
|
|
1072
1446
|
if (params.description !== void 0) body.description = params.description;
|
|
1073
1447
|
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;
|
|
1448
|
+
const touchesConfig = params.queryExtraction !== void 0 || params.callbackUrl !== void 0 || params.callbackMethod !== void 0 || params.responseTransform !== void 0 || params.mode !== void 0;
|
|
1075
1449
|
if (touchesConfig) {
|
|
1076
1450
|
const path = `/api/triggers/${encodeURIComponent(params.triggerId)}`;
|
|
1077
1451
|
const currentJson = rec(
|
|
@@ -1089,22 +1463,34 @@ async function updateTrigger(cfg, params) {
|
|
|
1089
1463
|
if (params.queryExtraction !== void 0) {
|
|
1090
1464
|
merged.query_extraction = queryExtractionBody(params.queryExtraction, void 0);
|
|
1091
1465
|
}
|
|
1092
|
-
|
|
1466
|
+
const touchesCallback = params.callbackUrl !== void 0 || params.callbackMethod !== void 0 || params.responseTransform !== void 0;
|
|
1467
|
+
if (params.mode !== void 0 || touchesCallback) {
|
|
1093
1468
|
const execution = rec(config.execution);
|
|
1094
1469
|
const callback = rec(execution.callback);
|
|
1095
1470
|
const newExecution = { ...execution };
|
|
1096
1471
|
if (params.mode !== void 0) newExecution.mode = params.mode;
|
|
1097
|
-
if (
|
|
1472
|
+
if (touchesCallback) {
|
|
1098
1473
|
const url = params.callbackUrl ?? str(callback.url);
|
|
1099
1474
|
if (!url) {
|
|
1100
1475
|
throw new ZihinAgentError(
|
|
1101
1476
|
"config",
|
|
1102
|
-
"callbackUrl is required: this trigger has no callback configured yet."
|
|
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."
|
|
1103
1478
|
);
|
|
1104
1479
|
}
|
|
1105
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,
|
|
1106
1486
|
url,
|
|
1107
|
-
method: params.callbackMethod ?? str(callback.method) ?? "POST"
|
|
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
|
+
} : {}
|
|
1108
1494
|
};
|
|
1109
1495
|
}
|
|
1110
1496
|
if (newExecution.mode === "async" && !rec(newExecution.callback).url) {
|
|
@@ -1133,7 +1519,7 @@ async function deleteTrigger(cfg, params) {
|
|
|
1133
1519
|
method: "DELETE",
|
|
1134
1520
|
url: `${cfg.baseUrl}/api/triggers/${encodeURIComponent(params.triggerId)}`,
|
|
1135
1521
|
headers: buildHeaders(cfg.apiKey),
|
|
1136
|
-
timeoutMs: cfg
|
|
1522
|
+
timeoutMs: resolveTimeout(cfg, "short"),
|
|
1137
1523
|
maxRetries: cfg.maxRetries,
|
|
1138
1524
|
policy: "idempotent",
|
|
1139
1525
|
// DELETE é idempotente
|
|
@@ -1170,9 +1556,11 @@ async function getExecution(cfg, params) {
|
|
|
1170
1556
|
const data = rec(json.data);
|
|
1171
1557
|
const exec = rec(data.execution ?? json.execution);
|
|
1172
1558
|
const metrics = rec(data.metrics ?? json.metrics);
|
|
1559
|
+
const rawStatus = str(exec.status);
|
|
1173
1560
|
return {
|
|
1174
1561
|
executionId: str(exec.id) ?? params.executionId,
|
|
1175
|
-
status:
|
|
1562
|
+
status: rawStatus ?? "unknown",
|
|
1563
|
+
terminal: rawStatus === void 0 ? false : isTerminalStatus(rawStatus),
|
|
1176
1564
|
startedAt: str(exec.started_at),
|
|
1177
1565
|
finishedAt: str(exec.finished_at),
|
|
1178
1566
|
executionTimeMs: num(exec.execution_time_ms),
|
|
@@ -1253,6 +1641,26 @@ var ZihinAgentClient = class {
|
|
|
1253
1641
|
getExecution(params) {
|
|
1254
1642
|
return this.guard(getExecution(this.cfg, params));
|
|
1255
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
|
+
}
|
|
1256
1664
|
// ---- Auxiliar ----
|
|
1257
1665
|
/** Lista os agentes do tenant (dropdown). */
|
|
1258
1666
|
listAgents(opts = {}) {
|
|
@@ -1292,10 +1700,14 @@ function signRequest(payload, secretKey, timestamp) {
|
|
|
1292
1700
|
}
|
|
1293
1701
|
// Annotate the CommonJS export names for ESM import in node:
|
|
1294
1702
|
0 && (module.exports = {
|
|
1703
|
+
DEADLINE_GRACE_MS,
|
|
1295
1704
|
DEFAULT_LIMITS,
|
|
1705
|
+
MAX_TASK_PAYLOAD_BYTES,
|
|
1706
|
+
OPERATION_TIMEOUT_MS,
|
|
1296
1707
|
ZihinAgentClient,
|
|
1297
1708
|
ZihinAgentError,
|
|
1298
1709
|
createClient,
|
|
1710
|
+
isCommercialBlock,
|
|
1299
1711
|
sanitizeApiError,
|
|
1300
1712
|
signRequest,
|
|
1301
1713
|
validateApiKeyFormat,
|