@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.mjs
CHANGED
|
@@ -29,6 +29,21 @@ function parseUsage(usageRaw) {
|
|
|
29
29
|
costUsd: num(u.cost_usd)
|
|
30
30
|
};
|
|
31
31
|
}
|
|
32
|
+
var RUNNING_STATUSES = /* @__PURE__ */ new Set([
|
|
33
|
+
"running",
|
|
34
|
+
"pending",
|
|
35
|
+
"queued",
|
|
36
|
+
"processing",
|
|
37
|
+
"in_progress",
|
|
38
|
+
"waiting",
|
|
39
|
+
"retrying",
|
|
40
|
+
"scheduled",
|
|
41
|
+
"awaiting_approval"
|
|
42
|
+
// HITL: turno pausado esperando decisão humana não terminou
|
|
43
|
+
]);
|
|
44
|
+
function isTerminalStatus(status) {
|
|
45
|
+
return !RUNNING_STATUSES.has(status.toLowerCase().replace(/-/g, "_"));
|
|
46
|
+
}
|
|
32
47
|
|
|
33
48
|
// src/security.ts
|
|
34
49
|
var PRIVATE_IP_PATTERNS = [
|
|
@@ -211,14 +226,19 @@ var SENSITIVE_PATTERNS = [
|
|
|
211
226
|
var ERROR_CODE_MAP = {
|
|
212
227
|
RATE_LIMIT_EXCEEDED: "Rate limit exceeded. Please wait before retrying.",
|
|
213
228
|
UNAUTHORIZED: "Authentication failed. Please check your API credentials.",
|
|
214
|
-
FORBIDDEN: "This API key
|
|
229
|
+
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.",
|
|
215
230
|
INVALID_MODEL: "The specified model is not available.",
|
|
216
231
|
INTERNAL_ERROR: "An internal error occurred. Please try again later.",
|
|
217
232
|
TIMEOUT: "Request timed out. Try increasing the timeout.",
|
|
218
233
|
INVALID_REQUEST: "The request was malformed. Please check your input.",
|
|
219
234
|
INSUFFICIENT_CREDITS: "Insufficient credits. Please check your account balance.",
|
|
220
235
|
CONTEXT_LENGTH_EXCEEDED: "The input exceeds the model context length limit.",
|
|
221
|
-
AGENT_NOT_FOUND: "Agent not found or not active for this tenant."
|
|
236
|
+
AGENT_NOT_FOUND: "Agent not found or not active for this tenant.",
|
|
237
|
+
// Bloqueios comerciais. As mensagens do BE vêm em português e estes shells são em inglês;
|
|
238
|
+
// além disso cada caso tem uma ação DIFERENTE, e o texto precisa dizer qual.
|
|
239
|
+
TRIAL_EXPIRED: "Your Zihin trial has ended. Upgrade the plan to keep invoking agents \u2014 the API key is fine.",
|
|
240
|
+
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.",
|
|
241
|
+
NO_ACTIVE_SUBSCRIPTION: "This tenant has no active Zihin subscription. Activate a plan to invoke agents \u2014 the API key is fine."
|
|
222
242
|
};
|
|
223
243
|
function sanitizeApiError(error) {
|
|
224
244
|
let message = "An unexpected error occurred";
|
|
@@ -231,8 +251,10 @@ function sanitizeApiError(error) {
|
|
|
231
251
|
const codeRaw = nested?.code ?? (typeof err.error === "string" ? err.error : err.code);
|
|
232
252
|
code = typeof codeRaw === "string" ? codeRaw : void 0;
|
|
233
253
|
const rawMessage = nested?.message ?? err.message;
|
|
234
|
-
|
|
235
|
-
|
|
254
|
+
const reason = typeof err.reason === "string" ? err.reason : void 0;
|
|
255
|
+
const effectiveCode = code === "QUOTA_EXCEEDED" && reason === "no_active_subscription" ? "NO_ACTIVE_SUBSCRIPTION" : code;
|
|
256
|
+
if (effectiveCode && ERROR_CODE_MAP[effectiveCode]) {
|
|
257
|
+
message = ERROR_CODE_MAP[effectiveCode];
|
|
236
258
|
} else if (typeof rawMessage === "string" && rawMessage) {
|
|
237
259
|
message = rawMessage;
|
|
238
260
|
}
|
|
@@ -281,6 +303,7 @@ var ZihinAgentError = class _ZihinAgentError extends Error {
|
|
|
281
303
|
this.status = opts.status;
|
|
282
304
|
this.code = opts.code;
|
|
283
305
|
this.requestId = opts.requestId;
|
|
306
|
+
this.executionId = opts.executionId;
|
|
284
307
|
this.attempts = opts.attempts;
|
|
285
308
|
this.endpoint = opts.endpoint;
|
|
286
309
|
this.retryable = opts.retryable ?? (kind === "rate_limit" || kind === "server" || kind === "network");
|
|
@@ -292,25 +315,65 @@ function buildMessage(base, opts) {
|
|
|
292
315
|
if (opts.endpoint) s += ` [${opts.endpoint}]`;
|
|
293
316
|
if (opts.attempts && opts.attempts > 1) s += ` (after ${opts.attempts} attempts)`;
|
|
294
317
|
if (opts.requestId) s += ` (request_id: ${opts.requestId})`;
|
|
318
|
+
if (opts.executionId) s += ` (execution_id: ${opts.executionId})`;
|
|
295
319
|
return s;
|
|
296
320
|
}
|
|
321
|
+
var COMMERCIAL_BLOCK_CODES = /* @__PURE__ */ new Set(["TRIAL_EXPIRED", "QUOTA_EXCEEDED", "NO_ACTIVE_SUBSCRIPTION"]);
|
|
322
|
+
function errorCodeOf(body) {
|
|
323
|
+
const b = rec(body);
|
|
324
|
+
return str(b.error) ?? str(rec(b.error).code) ?? str(b.code);
|
|
325
|
+
}
|
|
326
|
+
function isCommercialBlock(body) {
|
|
327
|
+
const code = errorCodeOf(body);
|
|
328
|
+
return code !== void 0 && COMMERCIAL_BLOCK_CODES.has(code);
|
|
329
|
+
}
|
|
330
|
+
function commercialKind(body) {
|
|
331
|
+
const code = errorCodeOf(body);
|
|
332
|
+
if (code === "TRIAL_EXPIRED") return "trial_expired";
|
|
333
|
+
if (code === "NO_ACTIVE_SUBSCRIPTION") return "no_subscription";
|
|
334
|
+
if (code === "QUOTA_EXCEEDED") {
|
|
335
|
+
return str(rec(body).reason) === "no_active_subscription" ? "no_subscription" : "quota_exceeded";
|
|
336
|
+
}
|
|
337
|
+
return void 0;
|
|
338
|
+
}
|
|
297
339
|
function extractRequestId(body) {
|
|
298
340
|
const b = rec(body);
|
|
299
|
-
return str(b.request_id) ?? str(b.requestId) ?? str(rec(b.error).request_id);
|
|
341
|
+
return str(b.request_id) ?? str(b.requestId) ?? str(rec(b.error).request_id) ?? str(rec(b.meta).request_id);
|
|
300
342
|
}
|
|
301
343
|
function errorFromResponse(status, body, meta = {}) {
|
|
302
344
|
const sanitized = sanitizeApiError(body);
|
|
303
345
|
const message = sanitized.message;
|
|
304
346
|
const code = sanitized.code;
|
|
305
|
-
const base = {
|
|
347
|
+
const base = {
|
|
348
|
+
status,
|
|
349
|
+
code,
|
|
350
|
+
requestId: extractRequestId(body),
|
|
351
|
+
executionId: str(rec(body).execution_id),
|
|
352
|
+
...meta
|
|
353
|
+
};
|
|
354
|
+
const commercial = commercialKind(body);
|
|
355
|
+
if (commercial) {
|
|
356
|
+
return new ZihinAgentError(commercial, message || "Request blocked by your plan.", {
|
|
357
|
+
...base,
|
|
358
|
+
retryable: false
|
|
359
|
+
});
|
|
360
|
+
}
|
|
306
361
|
if (status === 401) return new ZihinAgentError("auth", message || "Authentication failed.", base);
|
|
307
362
|
if (status === 403)
|
|
308
363
|
return new ZihinAgentError(
|
|
309
364
|
"auth",
|
|
310
|
-
|
|
365
|
+
// Não enumerar papéis aqui: o papel exigido varia por rota (triggers aceitam editor,
|
|
366
|
+
// mas delegar/cancelar uma task exige admin/owner). A frase antiga mandava o usuário
|
|
367
|
+
// para o papel errado em metade da API.
|
|
368
|
+
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.",
|
|
311
369
|
base
|
|
312
370
|
);
|
|
313
371
|
if (status === 404) return new ZihinAgentError("not_found", message || "Resource not found.", base);
|
|
372
|
+
if (status === 408)
|
|
373
|
+
return new ZihinAgentError("timeout", message || "The server timed out handling the request.", {
|
|
374
|
+
...base,
|
|
375
|
+
retryable: true
|
|
376
|
+
});
|
|
314
377
|
if (status === 429)
|
|
315
378
|
return new ZihinAgentError("rate_limit", message || "Rate limit exceeded.", { ...base, retryable: true });
|
|
316
379
|
if (status >= 500)
|
|
@@ -340,7 +403,25 @@ function errorFromThrow(err, timeoutMs, meta = {}) {
|
|
|
340
403
|
|
|
341
404
|
// src/http.ts
|
|
342
405
|
var DEFAULT_BASE_URL = "https://llm.zihin.ai";
|
|
343
|
-
var
|
|
406
|
+
var OPERATION_TIMEOUT_MS = Object.freeze({
|
|
407
|
+
stream: 2e5,
|
|
408
|
+
dispatch: 26e4,
|
|
409
|
+
short: 35e3
|
|
410
|
+
});
|
|
411
|
+
var DEADLINE_GRACE_MS = 1e4;
|
|
412
|
+
var RETRY_OVERHEAD_MS = 6e4;
|
|
413
|
+
var MIN_TIMEOUT_MS = 1e3;
|
|
414
|
+
var MAX_TIMEOUT_MS = 9e5;
|
|
415
|
+
function normalizeTimeoutMs(value, label = "timeoutMs") {
|
|
416
|
+
const n = typeof value === "number" ? value : Number(value);
|
|
417
|
+
if (!Number.isFinite(n) || n <= 0) {
|
|
418
|
+
throw new ZihinAgentError(
|
|
419
|
+
"config",
|
|
420
|
+
`${label} must be a positive number of milliseconds (got ${JSON.stringify(value)}).`
|
|
421
|
+
);
|
|
422
|
+
}
|
|
423
|
+
return Math.min(Math.max(n, MIN_TIMEOUT_MS), MAX_TIMEOUT_MS);
|
|
424
|
+
}
|
|
344
425
|
var DEFAULT_MAX_RETRIES = 2;
|
|
345
426
|
var DEFAULT_MAX_EVENT_BYTES = 1 * 1024 * 1024;
|
|
346
427
|
var DEFAULT_MAX_TOTAL_BYTES = 64 * 1024 * 1024;
|
|
@@ -373,7 +454,7 @@ function resolveConfig(config) {
|
|
|
373
454
|
return {
|
|
374
455
|
apiKey: config.apiKey.trim(),
|
|
375
456
|
baseUrl: urlCheck.sanitizedUrl || DEFAULT_BASE_URL,
|
|
376
|
-
timeoutMs: config.timeoutMs
|
|
457
|
+
timeoutMs: config.timeoutMs === void 0 || config.timeoutMs === null ? void 0 : normalizeTimeoutMs(config.timeoutMs, "timeoutMs"),
|
|
377
458
|
maxRetries: config.maxRetries ?? DEFAULT_MAX_RETRIES,
|
|
378
459
|
maxStreamEventBytes: config.maxStreamEventBytes ?? DEFAULT_MAX_EVENT_BYTES,
|
|
379
460
|
maxStreamTotalBytes: config.maxStreamTotalBytes ?? DEFAULT_MAX_TOTAL_BYTES,
|
|
@@ -382,6 +463,9 @@ function resolveConfig(config) {
|
|
|
382
463
|
onError: config.onError
|
|
383
464
|
};
|
|
384
465
|
}
|
|
466
|
+
function resolveTimeout(cfg, operation) {
|
|
467
|
+
return cfg.timeoutMs ?? OPERATION_TIMEOUT_MS[operation];
|
|
468
|
+
}
|
|
385
469
|
function endpointLabel(method, url) {
|
|
386
470
|
try {
|
|
387
471
|
const u = new URL(url);
|
|
@@ -455,12 +539,27 @@ function createDeadline(timeoutMs, callerSignal) {
|
|
|
455
539
|
};
|
|
456
540
|
}
|
|
457
541
|
function shouldRetryStatus(status, policy) {
|
|
458
|
-
if (policy === "idempotent") return status >= 500 || status === 429;
|
|
542
|
+
if (policy === "idempotent") return status >= 500 || status === 429 || status === 408;
|
|
459
543
|
return status === 429 || status === 503;
|
|
460
544
|
}
|
|
545
|
+
function budgetTracker(args) {
|
|
546
|
+
const startedAt = Date.now();
|
|
547
|
+
const reported = args.reportedTimeoutMs ?? args.timeoutMs;
|
|
548
|
+
const overheadCap = Math.min(args.timeoutMs, RETRY_OVERHEAD_MS);
|
|
549
|
+
return {
|
|
550
|
+
reported,
|
|
551
|
+
/** Quanto ainda pode ser gasto com tentativas falhas + backoff. */
|
|
552
|
+
overheadLeft: () => overheadCap - (Date.now() - startedAt),
|
|
553
|
+
expired: (attempts) => new ZihinAgentError("timeout", `Request timed out after ${reported}ms.`, {
|
|
554
|
+
endpoint: args.endpoint,
|
|
555
|
+
attempts
|
|
556
|
+
})
|
|
557
|
+
};
|
|
558
|
+
}
|
|
461
559
|
async function requestBuffered(args) {
|
|
462
560
|
const endpoint = endpointLabel(args.method, args.url);
|
|
463
561
|
const doFetch = resolveFetch(args.fetchImpl);
|
|
562
|
+
const budget = budgetTracker({ ...args, endpoint });
|
|
464
563
|
let lastError = null;
|
|
465
564
|
for (let attempt = 0; attempt <= args.maxRetries; attempt++) {
|
|
466
565
|
const d = createDeadline(args.timeoutMs, args.callerSignal);
|
|
@@ -475,30 +574,39 @@ async function requestBuffered(args) {
|
|
|
475
574
|
cache: "no-store"
|
|
476
575
|
// evita cache stale (ex.: polling em Next.js/edge)
|
|
477
576
|
});
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
577
|
+
let wait = shouldRetryStatus(res.status, args.policy) && attempt < args.maxRetries ? retryAfterMs(res.headers) ?? backoffMs(attempt) : null;
|
|
578
|
+
let peeked;
|
|
579
|
+
if (wait !== null && res.status === 429) {
|
|
580
|
+
peeked = await res.text();
|
|
581
|
+
if (isCommercialBlock(parseBody(peeked))) wait = null;
|
|
582
|
+
}
|
|
583
|
+
if (wait !== null && budget.overheadLeft() > wait) {
|
|
584
|
+
if (peeked === void 0) {
|
|
585
|
+
try {
|
|
586
|
+
await res.body?.cancel();
|
|
587
|
+
} catch {
|
|
588
|
+
}
|
|
483
589
|
}
|
|
484
590
|
d.dispose();
|
|
485
591
|
args.onRetry?.({ endpoint, attempt: attempt + 1, delayMs: wait, reason: "status", status: res.status });
|
|
486
592
|
await delay(wait, args.callerSignal);
|
|
487
593
|
continue;
|
|
488
594
|
}
|
|
489
|
-
const text = await res.text();
|
|
595
|
+
const text = peeked ?? await res.text();
|
|
490
596
|
d.dispose();
|
|
491
597
|
return { status: res.status, ok: res.ok, text, endpoint, attempts: attempt + 1 };
|
|
492
598
|
} catch (err) {
|
|
493
599
|
d.dispose();
|
|
600
|
+
if (err instanceof ZihinAgentError) throw err;
|
|
494
601
|
const meta = { endpoint, attempts: attempt + 1 };
|
|
495
|
-
if (d.timedOut()) throw
|
|
602
|
+
if (d.timedOut()) throw budget.expired(attempt + 1);
|
|
496
603
|
if (d.abortedByCaller())
|
|
497
604
|
throw new ZihinAgentError("aborted", "Request was aborted by the caller.", meta);
|
|
498
|
-
lastError = errorFromThrow(err,
|
|
605
|
+
lastError = errorFromThrow(err, budget.reported, meta);
|
|
499
606
|
const canRetry = args.policy === "idempotent" && lastError.retryable && attempt < args.maxRetries;
|
|
500
607
|
if (!canRetry) throw lastError;
|
|
501
608
|
const wait = backoffMs(attempt);
|
|
609
|
+
if (budget.overheadLeft() <= wait) throw lastError;
|
|
502
610
|
args.onRetry?.({ endpoint, attempt: attempt + 1, delayMs: wait, reason: "network" });
|
|
503
611
|
await delay(wait, args.callerSignal);
|
|
504
612
|
}
|
|
@@ -508,6 +616,7 @@ async function requestBuffered(args) {
|
|
|
508
616
|
async function openStream(args) {
|
|
509
617
|
const endpoint = endpointLabel("POST", args.url);
|
|
510
618
|
const doFetch = resolveFetch(args.fetchImpl);
|
|
619
|
+
const budget = budgetTracker({ ...args, endpoint });
|
|
511
620
|
for (let attempt = 0; attempt <= args.maxRetries; attempt++) {
|
|
512
621
|
const d = createDeadline(args.timeoutMs, args.callerSignal);
|
|
513
622
|
try {
|
|
@@ -519,8 +628,20 @@ async function openStream(args) {
|
|
|
519
628
|
redirect: "error",
|
|
520
629
|
cache: "no-store"
|
|
521
630
|
});
|
|
522
|
-
|
|
523
|
-
|
|
631
|
+
let wait = shouldRetryStatus(response.status, "side-effecting") && attempt < args.maxRetries ? retryAfterMs(response.headers) ?? backoffMs(attempt) : null;
|
|
632
|
+
if (response.status === 429) {
|
|
633
|
+
const text = await response.text().catch(() => "");
|
|
634
|
+
const body = parseBody(text);
|
|
635
|
+
if (wait === null || isCommercialBlock(body) || budget.overheadLeft() <= wait) {
|
|
636
|
+
d.dispose();
|
|
637
|
+
throw errorFromResponse(response.status, body, { endpoint, attempts: attempt + 1 });
|
|
638
|
+
}
|
|
639
|
+
d.dispose();
|
|
640
|
+
args.onRetry?.({ endpoint, attempt: attempt + 1, delayMs: wait, reason: "status", status: 429 });
|
|
641
|
+
await delay(wait, args.callerSignal);
|
|
642
|
+
continue;
|
|
643
|
+
}
|
|
644
|
+
if (wait !== null && budget.overheadLeft() > wait) {
|
|
524
645
|
try {
|
|
525
646
|
await response.body?.cancel();
|
|
526
647
|
} catch {
|
|
@@ -539,15 +660,51 @@ async function openStream(args) {
|
|
|
539
660
|
return { response, deadline: d, endpoint };
|
|
540
661
|
} catch (err) {
|
|
541
662
|
d.dispose();
|
|
663
|
+
if (err instanceof ZihinAgentError) throw err;
|
|
542
664
|
const meta = { endpoint, attempts: attempt + 1 };
|
|
543
|
-
if (d.timedOut()) throw
|
|
665
|
+
if (d.timedOut()) throw budget.expired(attempt + 1);
|
|
544
666
|
if (d.abortedByCaller())
|
|
545
667
|
throw new ZihinAgentError("aborted", "Request was aborted by the caller.", meta);
|
|
546
|
-
throw errorFromThrow(err,
|
|
668
|
+
throw errorFromThrow(err, budget.reported, meta);
|
|
547
669
|
}
|
|
548
670
|
}
|
|
549
671
|
throw new ZihinAgentError("unknown", "Max retries exceeded.", { endpoint });
|
|
550
672
|
}
|
|
673
|
+
function toJsonBody(value, label = "body") {
|
|
674
|
+
try {
|
|
675
|
+
return JSON.stringify(value);
|
|
676
|
+
} catch (err) {
|
|
677
|
+
throw new ZihinAgentError(
|
|
678
|
+
"config",
|
|
679
|
+
`${label} is not JSON-serializable: ${err?.message ?? "unknown reason"}.`
|
|
680
|
+
);
|
|
681
|
+
}
|
|
682
|
+
}
|
|
683
|
+
async function requestJson(cfg, args) {
|
|
684
|
+
const res = await requestBuffered({
|
|
685
|
+
method: args.method,
|
|
686
|
+
url: `${cfg.baseUrl}${args.path}`,
|
|
687
|
+
headers: buildHeaders(cfg.apiKey),
|
|
688
|
+
body: args.body !== void 0 ? toJsonBody(args.body) : void 0,
|
|
689
|
+
timeoutMs: resolveTimeout(cfg, args.operation ?? "short"),
|
|
690
|
+
maxRetries: cfg.maxRetries,
|
|
691
|
+
policy: args.policy,
|
|
692
|
+
callerSignal: args.signal,
|
|
693
|
+
fetchImpl: cfg.fetchImpl,
|
|
694
|
+
onRetry: cfg.onRetry
|
|
695
|
+
});
|
|
696
|
+
if (!res.ok) {
|
|
697
|
+
throw errorFromResponse(res.status, parseBody(res.text), {
|
|
698
|
+
endpoint: res.endpoint,
|
|
699
|
+
attempts: res.attempts
|
|
700
|
+
});
|
|
701
|
+
}
|
|
702
|
+
try {
|
|
703
|
+
return JSON.parse(res.text);
|
|
704
|
+
} catch {
|
|
705
|
+
throw new ZihinAgentError("protocol", "Expected JSON response.", { endpoint: res.endpoint });
|
|
706
|
+
}
|
|
707
|
+
}
|
|
551
708
|
function parseBody(text) {
|
|
552
709
|
if (!text) return {};
|
|
553
710
|
try {
|
|
@@ -563,7 +720,7 @@ async function listAgents(cfg, opts = {}) {
|
|
|
563
720
|
method: "GET",
|
|
564
721
|
url: `${cfg.baseUrl}/api/v2/agents`,
|
|
565
722
|
headers: buildHeaders(cfg.apiKey),
|
|
566
|
-
timeoutMs: cfg
|
|
723
|
+
timeoutMs: resolveTimeout(cfg, "short"),
|
|
567
724
|
maxRetries: cfg.maxRetries,
|
|
568
725
|
policy: "idempotent",
|
|
569
726
|
callerSignal: opts.signal,
|
|
@@ -730,6 +887,20 @@ function mapEvent(e) {
|
|
|
730
887
|
return { type: "token", content: str(d.content) ?? "", data: e.data };
|
|
731
888
|
case "warning":
|
|
732
889
|
return { type: "warning", message: str(d.message), data: e.data };
|
|
890
|
+
case "session":
|
|
891
|
+
return { type: "session", sessionId: str(d.session_id), data: e.data };
|
|
892
|
+
case "done":
|
|
893
|
+
return { type: "done", data: e.data };
|
|
894
|
+
case "error": {
|
|
895
|
+
const err = rec(d.error);
|
|
896
|
+
return {
|
|
897
|
+
type: "error",
|
|
898
|
+
code: str(err.code),
|
|
899
|
+
message: str(err.message),
|
|
900
|
+
requestId: str(err.request_id),
|
|
901
|
+
data: e.data
|
|
902
|
+
};
|
|
903
|
+
}
|
|
733
904
|
case "response":
|
|
734
905
|
return {
|
|
735
906
|
type: "response",
|
|
@@ -754,7 +925,7 @@ function mapEvent(e) {
|
|
|
754
925
|
return { type: "unknown", event: e.event, data: e.data };
|
|
755
926
|
}
|
|
756
927
|
}
|
|
757
|
-
function buildRequestBody(params) {
|
|
928
|
+
function buildRequestBody(params, normalizedTimeoutMs) {
|
|
758
929
|
const body = {};
|
|
759
930
|
if (params.message !== void 0) body.message = params.message;
|
|
760
931
|
if (params.sessionId) body.session_id = params.sessionId;
|
|
@@ -765,7 +936,7 @@ function buildRequestBody(params) {
|
|
|
765
936
|
const o = {};
|
|
766
937
|
if (params.options.temperature !== void 0) o.temperature = params.options.temperature;
|
|
767
938
|
if (params.options.maxTokens !== void 0) o.max_tokens = params.options.maxTokens;
|
|
768
|
-
if (
|
|
939
|
+
if (normalizedTimeoutMs !== void 0) o.timeout_ms = normalizedTimeoutMs;
|
|
769
940
|
if (params.options.jitMode !== void 0) o.jit_mode = params.options.jitMode;
|
|
770
941
|
if (Object.keys(o).length) body.options = o;
|
|
771
942
|
}
|
|
@@ -796,6 +967,9 @@ function validateParams(params) {
|
|
|
796
967
|
throw new ZihinAgentError("config", sizeCheck.error || "Message too large.");
|
|
797
968
|
}
|
|
798
969
|
}
|
|
970
|
+
if (params.options?.timeoutMs !== void 0) {
|
|
971
|
+
normalizeTimeoutMs(params.options.timeoutMs, "options.timeoutMs");
|
|
972
|
+
}
|
|
799
973
|
for (const att of params.attachments ?? []) {
|
|
800
974
|
const urlCheck = validateBaseUrl(att.url, { allowCustomDomains: true });
|
|
801
975
|
if (!urlCheck.valid) {
|
|
@@ -805,13 +979,16 @@ function validateParams(params) {
|
|
|
805
979
|
}
|
|
806
980
|
async function* streamAgent(cfg, params) {
|
|
807
981
|
validateParams(params);
|
|
808
|
-
const
|
|
982
|
+
const callerTimeoutMs = params.options?.timeoutMs !== void 0 ? normalizeTimeoutMs(params.options.timeoutMs, "options.timeoutMs") : void 0;
|
|
983
|
+
const timeoutMs = callerTimeoutMs !== void 0 ? callerTimeoutMs + DEADLINE_GRACE_MS : resolveTimeout(cfg, "stream");
|
|
984
|
+
const reportedMs = callerTimeoutMs ?? timeoutMs;
|
|
809
985
|
const url = `${cfg.baseUrl}/api/v2/agents/${encodeURIComponent(params.agentId)}/stream`;
|
|
810
986
|
const { response, deadline, endpoint } = await openStream({
|
|
811
987
|
url,
|
|
812
988
|
headers: buildHeaders(cfg.apiKey),
|
|
813
|
-
body:
|
|
989
|
+
body: toJsonBody(buildRequestBody(params, callerTimeoutMs), "request body"),
|
|
814
990
|
timeoutMs,
|
|
991
|
+
reportedTimeoutMs: reportedMs,
|
|
815
992
|
maxRetries: cfg.maxRetries,
|
|
816
993
|
callerSignal: params.signal,
|
|
817
994
|
fetchImpl: cfg.fetchImpl,
|
|
@@ -826,7 +1003,7 @@ async function* streamAgent(cfg, params) {
|
|
|
826
1003
|
}
|
|
827
1004
|
throw errorFromResponse(response.status, parseBody(text), { endpoint });
|
|
828
1005
|
}
|
|
829
|
-
const onAbortError = () => deadline.timedOut() ? new ZihinAgentError("timeout", `Stream timed out after ${
|
|
1006
|
+
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 });
|
|
830
1007
|
for await (const e of parseSse(response, {
|
|
831
1008
|
signal: params.signal,
|
|
832
1009
|
onAbortError,
|
|
@@ -845,19 +1022,36 @@ async function invokeAgent(cfg, params) {
|
|
|
845
1022
|
let responseChunk;
|
|
846
1023
|
let metricsChunk;
|
|
847
1024
|
let metadataSessionId;
|
|
1025
|
+
let mintedSessionId;
|
|
1026
|
+
let cleanEnd = false;
|
|
848
1027
|
let tokenBuffer = "";
|
|
849
1028
|
for await (const chunk of streamAgent(cfg, params)) {
|
|
850
1029
|
if (keepRaw) raw.push(chunk);
|
|
1030
|
+
if (chunk.type === "error") {
|
|
1031
|
+
const code = chunk.code ?? "AGENT_ERROR";
|
|
1032
|
+
const kind = /TIMEOUT/i.test(code) ? "timeout" : "server";
|
|
1033
|
+
throw new ZihinAgentError(kind, sanitizeApiError(chunk.message ?? code).message || code, {
|
|
1034
|
+
code,
|
|
1035
|
+
requestId: chunk.requestId,
|
|
1036
|
+
retryable: false
|
|
1037
|
+
});
|
|
1038
|
+
}
|
|
851
1039
|
if (chunk.type === "response") responseChunk = chunk;
|
|
852
1040
|
else if (chunk.type === "metrics") metricsChunk = chunk;
|
|
853
1041
|
else if (chunk.type === "token") tokenBuffer += chunk.content;
|
|
854
1042
|
else if (chunk.type === "metadata" && chunk.sessionId) metadataSessionId = chunk.sessionId;
|
|
1043
|
+
else if (chunk.type === "session" && chunk.sessionId) mintedSessionId = chunk.sessionId;
|
|
1044
|
+
else if (chunk.type === "done") cleanEnd = true;
|
|
855
1045
|
}
|
|
856
1046
|
if (!responseChunk) {
|
|
1047
|
+
if (cleanEnd) {
|
|
1048
|
+
throw new ZihinAgentError("protocol", "Stream ended (done) without a `response` event.");
|
|
1049
|
+
}
|
|
857
1050
|
if (tokenBuffer) {
|
|
858
1051
|
return {
|
|
859
1052
|
content: tokenBuffer,
|
|
860
|
-
|
|
1053
|
+
contentSource: "tokens",
|
|
1054
|
+
sessionId: mintedSessionId ?? metadataSessionId ?? params.sessionId,
|
|
861
1055
|
usage: metricsChunk?.usage,
|
|
862
1056
|
model: metricsChunk?.model,
|
|
863
1057
|
iterations: metricsChunk?.iterations,
|
|
@@ -868,8 +1062,9 @@ async function invokeAgent(cfg, params) {
|
|
|
868
1062
|
throw new ZihinAgentError("protocol", "Stream ended without a `response` event.");
|
|
869
1063
|
}
|
|
870
1064
|
return {
|
|
871
|
-
content: responseChunk.content
|
|
872
|
-
|
|
1065
|
+
content: responseChunk.content,
|
|
1066
|
+
contentSource: "response",
|
|
1067
|
+
sessionId: mintedSessionId ?? responseChunk.sessionId ?? metadataSessionId ?? params.sessionId,
|
|
873
1068
|
model: metricsChunk?.model,
|
|
874
1069
|
usage: metricsChunk?.usage,
|
|
875
1070
|
sources: responseChunk.sources,
|
|
@@ -880,29 +1075,180 @@ async function invokeAgent(cfg, params) {
|
|
|
880
1075
|
};
|
|
881
1076
|
}
|
|
882
1077
|
|
|
883
|
-
// src/
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
1078
|
+
// src/tasks.ts
|
|
1079
|
+
var MAX_TASK_PAYLOAD_BYTES = 64 * 1024;
|
|
1080
|
+
var SCHEDULE_PAST_TOLERANCE_MS = 6e4;
|
|
1081
|
+
function envelope(json) {
|
|
1082
|
+
const j = rec(json);
|
|
1083
|
+
return { data: rec(j.data ?? j), meta: rec(j.meta) };
|
|
1084
|
+
}
|
|
1085
|
+
function toIso(value, label) {
|
|
1086
|
+
if (value === void 0 || value === null || value === "") return void 0;
|
|
1087
|
+
const d = value instanceof Date ? value : new Date(value);
|
|
1088
|
+
if (Number.isNaN(d.getTime())) {
|
|
1089
|
+
throw new ZihinAgentError("config", `${label} must be a valid ISO 8601 date (got ${JSON.stringify(value)}).`);
|
|
1090
|
+
}
|
|
1091
|
+
return d.toISOString();
|
|
1092
|
+
}
|
|
1093
|
+
function mapTask(raw) {
|
|
1094
|
+
const t = rec(raw);
|
|
1095
|
+
const status = str(t.status);
|
|
1096
|
+
return {
|
|
1097
|
+
id: str(t.id) ?? "",
|
|
1098
|
+
agentId: str(t.agent_id),
|
|
1099
|
+
title: str(t.title),
|
|
1100
|
+
instruction: str(t.instruction),
|
|
1101
|
+
status: status ?? "unknown",
|
|
1102
|
+
// Mesma regra do Caminho B, e de propósito a MESMA função: status desconhecido conta como
|
|
1103
|
+
// terminal (o polling para em vez de girar), status ausente não (não lemos o envelope).
|
|
1104
|
+
terminal: status === void 0 ? false : isTerminalStatus(status),
|
|
1105
|
+
scheduledFor: str(t.scheduled_for),
|
|
1106
|
+
deadlineAt: str(t.deadline_at),
|
|
1107
|
+
parentTaskId: str(t.parent_task_id),
|
|
1108
|
+
rootTaskId: str(t.root_task_id),
|
|
1109
|
+
sessionId: str(t.session_id),
|
|
1110
|
+
createdAt: str(t.created_at),
|
|
1111
|
+
updatedAt: str(t.updated_at),
|
|
1112
|
+
raw
|
|
1113
|
+
};
|
|
1114
|
+
}
|
|
1115
|
+
function mapExecution(raw) {
|
|
1116
|
+
const e = rec(raw);
|
|
1117
|
+
return {
|
|
1118
|
+
id: str(e.id) ?? "",
|
|
1119
|
+
status: str(e.status) ?? "unknown",
|
|
1120
|
+
exitReason: str(e.exit_reason),
|
|
1121
|
+
modelUsed: str(e.model_used),
|
|
1122
|
+
totalCostUsd: num(e.total_cost_usd),
|
|
1123
|
+
executionTimeMs: num(e.execution_time_ms),
|
|
1124
|
+
iterations: num(e.iterations),
|
|
1125
|
+
startedAt: str(e.started_at),
|
|
1126
|
+
finishedAt: str(e.finished_at),
|
|
1127
|
+
raw
|
|
1128
|
+
};
|
|
1129
|
+
}
|
|
1130
|
+
async function delegateTask(cfg, params) {
|
|
1131
|
+
if (!params.agentId) throw new ZihinAgentError("config", "agentId is required.");
|
|
1132
|
+
if (!params.instruction) throw new ZihinAgentError("config", "instruction is required.");
|
|
1133
|
+
const scheduledFor = toIso(params.scheduledFor, "scheduledFor");
|
|
1134
|
+
const deadlineAt = toIso(params.deadlineAt, "deadlineAt");
|
|
1135
|
+
if (scheduledFor && Date.parse(scheduledFor) < Date.now() - SCHEDULE_PAST_TOLERANCE_MS) {
|
|
1136
|
+
throw new ZihinAgentError("config", "scheduledFor is in the past.");
|
|
1137
|
+
}
|
|
1138
|
+
if (deadlineAt && Date.parse(deadlineAt) <= Date.now() - SCHEDULE_PAST_TOLERANCE_MS) {
|
|
1139
|
+
throw new ZihinAgentError("config", "deadlineAt must be in the future.");
|
|
1140
|
+
}
|
|
1141
|
+
if (scheduledFor && deadlineAt && Date.parse(deadlineAt) <= Date.parse(scheduledFor)) {
|
|
1142
|
+
throw new ZihinAgentError("config", "deadlineAt must be after scheduledFor.");
|
|
1143
|
+
}
|
|
1144
|
+
if (params.payload !== void 0) {
|
|
1145
|
+
if (typeof params.payload !== "object" || params.payload === null || Array.isArray(params.payload)) {
|
|
1146
|
+
throw new ZihinAgentError("config", "payload must be an object.");
|
|
1147
|
+
}
|
|
1148
|
+
const bytes = toJsonBody(params.payload, "payload").length;
|
|
1149
|
+
if (bytes > MAX_TASK_PAYLOAD_BYTES) {
|
|
1150
|
+
throw new ZihinAgentError(
|
|
1151
|
+
"config",
|
|
1152
|
+
`payload exceeds ${MAX_TASK_PAYLOAD_BYTES / 1024}KB (got ${Math.ceil(bytes / 1024)}KB).`
|
|
1153
|
+
);
|
|
1154
|
+
}
|
|
1155
|
+
}
|
|
1156
|
+
const body = {
|
|
1157
|
+
agent_id: params.agentId,
|
|
1158
|
+
instruction: params.instruction
|
|
1159
|
+
};
|
|
1160
|
+
if (params.title !== void 0) body.title = params.title;
|
|
1161
|
+
if (params.payload !== void 0) body.payload = params.payload;
|
|
1162
|
+
if (scheduledFor) body.scheduled_for = scheduledFor;
|
|
1163
|
+
if (deadlineAt) body.deadline_at = deadlineAt;
|
|
1164
|
+
if (params.retryPolicy !== void 0) body.retry_policy = params.retryPolicy;
|
|
1165
|
+
if (params.notify !== void 0) body.notify = params.notify;
|
|
1166
|
+
if (params.parentTaskId) body.parent_task_id = params.parentTaskId;
|
|
1167
|
+
const json = await requestJson(cfg, {
|
|
1168
|
+
method: "POST",
|
|
1169
|
+
path: "/api/v1/tasks",
|
|
1170
|
+
body,
|
|
1171
|
+
// Criar uma task E dispará-la é efeito colateral: nunca repetir em erro de rede.
|
|
1172
|
+
policy: "side-effecting",
|
|
1173
|
+
signal: params.signal
|
|
896
1174
|
});
|
|
897
|
-
|
|
898
|
-
|
|
1175
|
+
const { data } = envelope(json);
|
|
1176
|
+
const task = mapTask(data.task ?? data);
|
|
1177
|
+
if (!task.id) {
|
|
1178
|
+
throw new ZihinAgentError("protocol", "delegateTask: response has no task id.");
|
|
899
1179
|
}
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
1180
|
+
return task;
|
|
1181
|
+
}
|
|
1182
|
+
async function listTasks(cfg, params = {}) {
|
|
1183
|
+
const qs = new URLSearchParams();
|
|
1184
|
+
if (params.live) qs.set("live", "true");
|
|
1185
|
+
if (params.status) qs.set("status", params.status);
|
|
1186
|
+
if (params.agentId) qs.set("agent_id", params.agentId);
|
|
1187
|
+
if (params.rootTaskId) qs.set("root_task_id", params.rootTaskId);
|
|
1188
|
+
if (params.limit !== void 0) qs.set("limit", String(params.limit));
|
|
1189
|
+
if (params.offset !== void 0) qs.set("offset", String(params.offset));
|
|
1190
|
+
const query = qs.toString();
|
|
1191
|
+
const json = await requestJson(cfg, {
|
|
1192
|
+
method: "GET",
|
|
1193
|
+
path: `/api/v1/tasks${query ? `?${query}` : ""}`,
|
|
1194
|
+
policy: "idempotent",
|
|
1195
|
+
signal: params.signal
|
|
1196
|
+
});
|
|
1197
|
+
const { data, meta } = envelope(json);
|
|
1198
|
+
const items = Array.isArray(data.items) ? data.items : [];
|
|
1199
|
+
return {
|
|
1200
|
+
items: items.map(mapTask),
|
|
1201
|
+
// ⚠️ `total` vive em `meta`, não no topo — o envelope novo aninhou os campos de paginação.
|
|
1202
|
+
total: num(meta.total),
|
|
1203
|
+
raw: json
|
|
1204
|
+
};
|
|
1205
|
+
}
|
|
1206
|
+
async function getTask(cfg, params) {
|
|
1207
|
+
if (!params.taskId) throw new ZihinAgentError("config", "taskId is required.");
|
|
1208
|
+
const json = await requestJson(cfg, {
|
|
1209
|
+
method: "GET",
|
|
1210
|
+
path: `/api/v1/tasks/${encodeURIComponent(params.taskId)}`,
|
|
1211
|
+
policy: "idempotent",
|
|
1212
|
+
signal: params.signal
|
|
1213
|
+
});
|
|
1214
|
+
const { data } = envelope(json);
|
|
1215
|
+
const taskRaw = data.task ?? (str(rec(data).id) ? data : void 0);
|
|
1216
|
+
if (!taskRaw || !str(rec(taskRaw).id)) {
|
|
1217
|
+
throw new ZihinAgentError("protocol", "getTask: response has no task.");
|
|
904
1218
|
}
|
|
1219
|
+
const executions = Array.isArray(data.executions) ? data.executions : [];
|
|
1220
|
+
const children = Array.isArray(data.children) ? data.children : [];
|
|
1221
|
+
return {
|
|
1222
|
+
task: mapTask(taskRaw),
|
|
1223
|
+
executions: executions.map(mapExecution),
|
|
1224
|
+
children: children.map(mapTask),
|
|
1225
|
+
approvals: Array.isArray(data.approvals) ? data.approvals : [],
|
|
1226
|
+
raw: json
|
|
1227
|
+
};
|
|
905
1228
|
}
|
|
1229
|
+
async function cancelTask(cfg, params) {
|
|
1230
|
+
if (!params.taskId) throw new ZihinAgentError("config", "taskId is required.");
|
|
1231
|
+
const json = await requestJson(cfg, {
|
|
1232
|
+
method: "POST",
|
|
1233
|
+
path: `/api/v1/tasks/${encodeURIComponent(params.taskId)}/cancel`,
|
|
1234
|
+
body: params.reason ? { reason: params.reason } : {},
|
|
1235
|
+
// Cancelar é side-effecting; repetir num socket caído bateria em 409 e confundiria o chamador.
|
|
1236
|
+
policy: "side-effecting",
|
|
1237
|
+
signal: params.signal
|
|
1238
|
+
});
|
|
1239
|
+
const { data } = envelope(json);
|
|
1240
|
+
const taskRaw = data.task ?? (str(rec(data).id) ? data : void 0);
|
|
1241
|
+
if (!taskRaw || !str(rec(taskRaw).id)) {
|
|
1242
|
+
throw new ZihinAgentError("protocol", "cancelTask: response has no task.");
|
|
1243
|
+
}
|
|
1244
|
+
return {
|
|
1245
|
+
task: mapTask(taskRaw),
|
|
1246
|
+
aborted: data.aborted === true,
|
|
1247
|
+
raw: json
|
|
1248
|
+
};
|
|
1249
|
+
}
|
|
1250
|
+
|
|
1251
|
+
// src/triggers.ts
|
|
906
1252
|
function queryExtractionBody(extraction, queryField) {
|
|
907
1253
|
if (!extraction) return { mode: "field", field: queryField ?? "message" };
|
|
908
1254
|
switch (extraction.mode) {
|
|
@@ -935,11 +1281,18 @@ async function createTrigger(cfg, params) {
|
|
|
935
1281
|
throw new ZihinAgentError("config", `Invalid callbackUrl: ${cb.error}`);
|
|
936
1282
|
}
|
|
937
1283
|
}
|
|
1284
|
+
if (params.responseTransform && !params.callbackUrl) {
|
|
1285
|
+
throw new ZihinAgentError(
|
|
1286
|
+
"config",
|
|
1287
|
+
"responseTransform requires callbackUrl: the backend only reads it from trigger_config.execution.callback.response_transform, and a callback without a url is rejected."
|
|
1288
|
+
);
|
|
1289
|
+
}
|
|
938
1290
|
const execution = { mode };
|
|
939
1291
|
if (params.callbackUrl) {
|
|
940
1292
|
execution.callback = {
|
|
941
1293
|
url: params.callbackUrl,
|
|
942
|
-
method: params.callbackMethod ?? "POST"
|
|
1294
|
+
method: params.callbackMethod ?? "POST",
|
|
1295
|
+
...params.responseTransform ? { response_transform: { mode: params.responseTransform } } : {}
|
|
943
1296
|
};
|
|
944
1297
|
}
|
|
945
1298
|
const body = {
|
|
@@ -969,8 +1322,8 @@ async function dispatchAgent(cfg, params) {
|
|
|
969
1322
|
method: "POST",
|
|
970
1323
|
url: `${cfg.baseUrl}/api/triggers/webhook/${encodeURIComponent(params.triggerId)}`,
|
|
971
1324
|
headers: buildHeaders(cfg.apiKey),
|
|
972
|
-
body:
|
|
973
|
-
timeoutMs: cfg
|
|
1325
|
+
body: toJsonBody(params.payload ?? {}, "payload"),
|
|
1326
|
+
timeoutMs: resolveTimeout(cfg, "dispatch"),
|
|
974
1327
|
maxRetries: cfg.maxRetries,
|
|
975
1328
|
policy: "side-effecting",
|
|
976
1329
|
// S1: nunca duplica disparo por erro de rede
|
|
@@ -983,12 +1336,29 @@ async function dispatchAgent(cfg, params) {
|
|
|
983
1336
|
}
|
|
984
1337
|
const json = parseBody(res.text);
|
|
985
1338
|
const j = rec(json);
|
|
1339
|
+
if (j.success === false) {
|
|
1340
|
+
const detail = sanitizeApiError(str(j.error) ?? "").message;
|
|
1341
|
+
throw new ZihinAgentError("server", detail || "The agent run failed.", {
|
|
1342
|
+
status: res.status,
|
|
1343
|
+
endpoint: res.endpoint,
|
|
1344
|
+
attempts: res.attempts,
|
|
1345
|
+
executionId: str(j.execution_id),
|
|
1346
|
+
// Não é transitório: o turno rodou e falhou. Repetir gastaria outra execução.
|
|
1347
|
+
retryable: false
|
|
1348
|
+
});
|
|
1349
|
+
}
|
|
986
1350
|
return {
|
|
987
1351
|
accepted: true,
|
|
988
1352
|
triggerId: str(j.trigger_id) ?? params.triggerId,
|
|
1353
|
+
agentId: str(j.agent_id),
|
|
989
1354
|
executionId: str(j.execution_id),
|
|
990
1355
|
agentResponse: str(j.agent_response),
|
|
991
1356
|
usage: parseUsage(j.usage),
|
|
1357
|
+
iterations: num(j.iterations),
|
|
1358
|
+
latencyMs: num(j.latency_ms),
|
|
1359
|
+
// Qual texto o BE extraiu do payload — é o campo que denuncia um `query_extraction` mal
|
|
1360
|
+
// configurado (mensagem vazia, ou o payload inteiro serializado virando a pergunta).
|
|
1361
|
+
queryExtracted: str(j.query_extracted),
|
|
992
1362
|
raw: json
|
|
993
1363
|
};
|
|
994
1364
|
}
|
|
@@ -1044,7 +1414,7 @@ async function updateTrigger(cfg, params) {
|
|
|
1044
1414
|
if (params.name !== void 0) body.name = params.name;
|
|
1045
1415
|
if (params.description !== void 0) body.description = params.description;
|
|
1046
1416
|
if (params.enabled !== void 0) body.enabled = params.enabled;
|
|
1047
|
-
const touchesConfig = params.queryExtraction !== void 0 || params.callbackUrl !== void 0 || params.callbackMethod !== void 0 || params.mode !== void 0;
|
|
1417
|
+
const touchesConfig = params.queryExtraction !== void 0 || params.callbackUrl !== void 0 || params.callbackMethod !== void 0 || params.responseTransform !== void 0 || params.mode !== void 0;
|
|
1048
1418
|
if (touchesConfig) {
|
|
1049
1419
|
const path = `/api/triggers/${encodeURIComponent(params.triggerId)}`;
|
|
1050
1420
|
const currentJson = rec(
|
|
@@ -1062,22 +1432,34 @@ async function updateTrigger(cfg, params) {
|
|
|
1062
1432
|
if (params.queryExtraction !== void 0) {
|
|
1063
1433
|
merged.query_extraction = queryExtractionBody(params.queryExtraction, void 0);
|
|
1064
1434
|
}
|
|
1065
|
-
|
|
1435
|
+
const touchesCallback = params.callbackUrl !== void 0 || params.callbackMethod !== void 0 || params.responseTransform !== void 0;
|
|
1436
|
+
if (params.mode !== void 0 || touchesCallback) {
|
|
1066
1437
|
const execution = rec(config.execution);
|
|
1067
1438
|
const callback = rec(execution.callback);
|
|
1068
1439
|
const newExecution = { ...execution };
|
|
1069
1440
|
if (params.mode !== void 0) newExecution.mode = params.mode;
|
|
1070
|
-
if (
|
|
1441
|
+
if (touchesCallback) {
|
|
1071
1442
|
const url = params.callbackUrl ?? str(callback.url);
|
|
1072
1443
|
if (!url) {
|
|
1073
1444
|
throw new ZihinAgentError(
|
|
1074
1445
|
"config",
|
|
1075
|
-
"callbackUrl is required: this trigger has no callback configured yet."
|
|
1446
|
+
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."
|
|
1076
1447
|
);
|
|
1077
1448
|
}
|
|
1078
1449
|
newExecution.callback = {
|
|
1450
|
+
// Preserva o callback atual: trocar só a URL não pode apagar auth/headers/
|
|
1451
|
+
// body_template/content_type/response_transform. Antes este objeto era
|
|
1452
|
+
// SUBSTITUÍDO por { url, method } — a mesma perda silenciosa que o merge de
|
|
1453
|
+
// trigger_config acima existe para evitar, um nível mais fundo.
|
|
1454
|
+
...callback,
|
|
1079
1455
|
url,
|
|
1080
|
-
method: params.callbackMethod ?? str(callback.method) ?? "POST"
|
|
1456
|
+
method: params.callbackMethod ?? str(callback.method) ?? "POST",
|
|
1457
|
+
...params.responseTransform ? {
|
|
1458
|
+
response_transform: {
|
|
1459
|
+
...rec(callback.response_transform),
|
|
1460
|
+
mode: params.responseTransform
|
|
1461
|
+
}
|
|
1462
|
+
} : {}
|
|
1081
1463
|
};
|
|
1082
1464
|
}
|
|
1083
1465
|
if (newExecution.mode === "async" && !rec(newExecution.callback).url) {
|
|
@@ -1106,7 +1488,7 @@ async function deleteTrigger(cfg, params) {
|
|
|
1106
1488
|
method: "DELETE",
|
|
1107
1489
|
url: `${cfg.baseUrl}/api/triggers/${encodeURIComponent(params.triggerId)}`,
|
|
1108
1490
|
headers: buildHeaders(cfg.apiKey),
|
|
1109
|
-
timeoutMs: cfg
|
|
1491
|
+
timeoutMs: resolveTimeout(cfg, "short"),
|
|
1110
1492
|
maxRetries: cfg.maxRetries,
|
|
1111
1493
|
policy: "idempotent",
|
|
1112
1494
|
// DELETE é idempotente
|
|
@@ -1143,9 +1525,11 @@ async function getExecution(cfg, params) {
|
|
|
1143
1525
|
const data = rec(json.data);
|
|
1144
1526
|
const exec = rec(data.execution ?? json.execution);
|
|
1145
1527
|
const metrics = rec(data.metrics ?? json.metrics);
|
|
1528
|
+
const rawStatus = str(exec.status);
|
|
1146
1529
|
return {
|
|
1147
1530
|
executionId: str(exec.id) ?? params.executionId,
|
|
1148
|
-
status:
|
|
1531
|
+
status: rawStatus ?? "unknown",
|
|
1532
|
+
terminal: rawStatus === void 0 ? false : isTerminalStatus(rawStatus),
|
|
1149
1533
|
startedAt: str(exec.started_at),
|
|
1150
1534
|
finishedAt: str(exec.finished_at),
|
|
1151
1535
|
executionTimeMs: num(exec.execution_time_ms),
|
|
@@ -1226,6 +1610,26 @@ var ZihinAgentClient = class {
|
|
|
1226
1610
|
getExecution(params) {
|
|
1227
1611
|
return this.guard(getExecution(this.cfg, params));
|
|
1228
1612
|
}
|
|
1613
|
+
// ---- Caminho C: tasks (delegação assíncrona durável) ----
|
|
1614
|
+
/**
|
|
1615
|
+
* Delega uma tarefa a um agente (requer key owner/admin). Sem `scheduledFor`, o BE dispara na
|
|
1616
|
+
* hora fora do request e devolve só o handle — acompanhe com `getTask`.
|
|
1617
|
+
*/
|
|
1618
|
+
delegateTask(params) {
|
|
1619
|
+
return this.guard(delegateTask(this.cfg, params));
|
|
1620
|
+
}
|
|
1621
|
+
/** Board de tasks do tenant (requer key owner/admin/editor). */
|
|
1622
|
+
listTasks(params = {}) {
|
|
1623
|
+
return this.guard(listTasks(this.cfg, params));
|
|
1624
|
+
}
|
|
1625
|
+
/** Dossiê de uma task: task + execuções + filhos + aprovações. */
|
|
1626
|
+
getTask(params) {
|
|
1627
|
+
return this.guard(getTask(this.cfg, params));
|
|
1628
|
+
}
|
|
1629
|
+
/** Cancela uma task (requer key owner/admin); derruba o turno em voo, se houver. */
|
|
1630
|
+
cancelTask(params) {
|
|
1631
|
+
return this.guard(cancelTask(this.cfg, params));
|
|
1632
|
+
}
|
|
1229
1633
|
// ---- Auxiliar ----
|
|
1230
1634
|
/** Lista os agentes do tenant (dropdown). */
|
|
1231
1635
|
listAgents(opts = {}) {
|
|
@@ -1264,10 +1668,14 @@ function signRequest(payload, secretKey, timestamp) {
|
|
|
1264
1668
|
};
|
|
1265
1669
|
}
|
|
1266
1670
|
export {
|
|
1671
|
+
DEADLINE_GRACE_MS,
|
|
1267
1672
|
DEFAULT_LIMITS,
|
|
1673
|
+
MAX_TASK_PAYLOAD_BYTES,
|
|
1674
|
+
OPERATION_TIMEOUT_MS,
|
|
1268
1675
|
ZihinAgentClient,
|
|
1269
1676
|
ZihinAgentError,
|
|
1270
1677
|
createClient,
|
|
1678
|
+
isCommercialBlock,
|
|
1271
1679
|
sanitizeApiError,
|
|
1272
1680
|
signRequest,
|
|
1273
1681
|
validateApiKeyFormat,
|