@zihin/agent-client 0.2.0 → 0.4.1
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 +73 -2
- package/dist/index.d.mts +318 -4
- package/dist/index.d.ts +318 -4
- package/dist/index.js +485 -60
- package/dist/index.mjs +479 -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,21 @@ 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
|
+
timedOut: typeof err.timed_out === "boolean" ? err.timed_out : void 0,
|
|
902
|
+
data: e.data
|
|
903
|
+
};
|
|
904
|
+
}
|
|
733
905
|
case "response":
|
|
734
906
|
return {
|
|
735
907
|
type: "response",
|
|
@@ -754,7 +926,7 @@ function mapEvent(e) {
|
|
|
754
926
|
return { type: "unknown", event: e.event, data: e.data };
|
|
755
927
|
}
|
|
756
928
|
}
|
|
757
|
-
function buildRequestBody(params) {
|
|
929
|
+
function buildRequestBody(params, normalizedTimeoutMs) {
|
|
758
930
|
const body = {};
|
|
759
931
|
if (params.message !== void 0) body.message = params.message;
|
|
760
932
|
if (params.sessionId) body.session_id = params.sessionId;
|
|
@@ -765,7 +937,7 @@ function buildRequestBody(params) {
|
|
|
765
937
|
const o = {};
|
|
766
938
|
if (params.options.temperature !== void 0) o.temperature = params.options.temperature;
|
|
767
939
|
if (params.options.maxTokens !== void 0) o.max_tokens = params.options.maxTokens;
|
|
768
|
-
if (
|
|
940
|
+
if (normalizedTimeoutMs !== void 0) o.timeout_ms = normalizedTimeoutMs;
|
|
769
941
|
if (params.options.jitMode !== void 0) o.jit_mode = params.options.jitMode;
|
|
770
942
|
if (Object.keys(o).length) body.options = o;
|
|
771
943
|
}
|
|
@@ -796,6 +968,9 @@ function validateParams(params) {
|
|
|
796
968
|
throw new ZihinAgentError("config", sizeCheck.error || "Message too large.");
|
|
797
969
|
}
|
|
798
970
|
}
|
|
971
|
+
if (params.options?.timeoutMs !== void 0) {
|
|
972
|
+
normalizeTimeoutMs(params.options.timeoutMs, "options.timeoutMs");
|
|
973
|
+
}
|
|
799
974
|
for (const att of params.attachments ?? []) {
|
|
800
975
|
const urlCheck = validateBaseUrl(att.url, { allowCustomDomains: true });
|
|
801
976
|
if (!urlCheck.valid) {
|
|
@@ -805,13 +980,16 @@ function validateParams(params) {
|
|
|
805
980
|
}
|
|
806
981
|
async function* streamAgent(cfg, params) {
|
|
807
982
|
validateParams(params);
|
|
808
|
-
const
|
|
983
|
+
const callerTimeoutMs = params.options?.timeoutMs !== void 0 ? normalizeTimeoutMs(params.options.timeoutMs, "options.timeoutMs") : void 0;
|
|
984
|
+
const timeoutMs = callerTimeoutMs !== void 0 ? callerTimeoutMs + DEADLINE_GRACE_MS : resolveTimeout(cfg, "stream");
|
|
985
|
+
const reportedMs = callerTimeoutMs ?? timeoutMs;
|
|
809
986
|
const url = `${cfg.baseUrl}/api/v2/agents/${encodeURIComponent(params.agentId)}/stream`;
|
|
810
987
|
const { response, deadline, endpoint } = await openStream({
|
|
811
988
|
url,
|
|
812
989
|
headers: buildHeaders(cfg.apiKey),
|
|
813
|
-
body:
|
|
990
|
+
body: toJsonBody(buildRequestBody(params, callerTimeoutMs), "request body"),
|
|
814
991
|
timeoutMs,
|
|
992
|
+
reportedTimeoutMs: reportedMs,
|
|
815
993
|
maxRetries: cfg.maxRetries,
|
|
816
994
|
callerSignal: params.signal,
|
|
817
995
|
fetchImpl: cfg.fetchImpl,
|
|
@@ -826,7 +1004,7 @@ async function* streamAgent(cfg, params) {
|
|
|
826
1004
|
}
|
|
827
1005
|
throw errorFromResponse(response.status, parseBody(text), { endpoint });
|
|
828
1006
|
}
|
|
829
|
-
const onAbortError = () => deadline.timedOut() ? new ZihinAgentError("timeout", `Stream timed out after ${
|
|
1007
|
+
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
1008
|
for await (const e of parseSse(response, {
|
|
831
1009
|
signal: params.signal,
|
|
832
1010
|
onAbortError,
|
|
@@ -839,25 +1017,50 @@ async function* streamAgent(cfg, params) {
|
|
|
839
1017
|
deadline.dispose();
|
|
840
1018
|
}
|
|
841
1019
|
}
|
|
1020
|
+
function classifyStreamErrorKind(code, timedOut) {
|
|
1021
|
+
if (timedOut === true) return "timeout";
|
|
1022
|
+
if (timedOut === false) return "server";
|
|
1023
|
+
return /TIMEOUT/i.test(code) ? "timeout" : "server";
|
|
1024
|
+
}
|
|
1025
|
+
function errorFromStreamChunk(chunk) {
|
|
1026
|
+
const code = chunk.code ?? "AGENT_ERROR";
|
|
1027
|
+
const kind = classifyStreamErrorKind(code, chunk.timedOut);
|
|
1028
|
+
return new ZihinAgentError(kind, sanitizeApiError(chunk.message ?? code).message || code, {
|
|
1029
|
+
code,
|
|
1030
|
+
requestId: chunk.requestId,
|
|
1031
|
+
retryable: false
|
|
1032
|
+
});
|
|
1033
|
+
}
|
|
842
1034
|
async function invokeAgent(cfg, params) {
|
|
843
1035
|
const raw = [];
|
|
844
1036
|
const keepRaw = params.includeRaw === true;
|
|
845
1037
|
let responseChunk;
|
|
846
1038
|
let metricsChunk;
|
|
847
1039
|
let metadataSessionId;
|
|
1040
|
+
let mintedSessionId;
|
|
1041
|
+
let cleanEnd = false;
|
|
848
1042
|
let tokenBuffer = "";
|
|
849
1043
|
for await (const chunk of streamAgent(cfg, params)) {
|
|
850
1044
|
if (keepRaw) raw.push(chunk);
|
|
1045
|
+
if (chunk.type === "error") {
|
|
1046
|
+
throw errorFromStreamChunk(chunk);
|
|
1047
|
+
}
|
|
851
1048
|
if (chunk.type === "response") responseChunk = chunk;
|
|
852
1049
|
else if (chunk.type === "metrics") metricsChunk = chunk;
|
|
853
1050
|
else if (chunk.type === "token") tokenBuffer += chunk.content;
|
|
854
1051
|
else if (chunk.type === "metadata" && chunk.sessionId) metadataSessionId = chunk.sessionId;
|
|
1052
|
+
else if (chunk.type === "session" && chunk.sessionId) mintedSessionId = chunk.sessionId;
|
|
1053
|
+
else if (chunk.type === "done") cleanEnd = true;
|
|
855
1054
|
}
|
|
856
1055
|
if (!responseChunk) {
|
|
1056
|
+
if (cleanEnd) {
|
|
1057
|
+
throw new ZihinAgentError("protocol", "Stream ended (done) without a `response` event.");
|
|
1058
|
+
}
|
|
857
1059
|
if (tokenBuffer) {
|
|
858
1060
|
return {
|
|
859
1061
|
content: tokenBuffer,
|
|
860
|
-
|
|
1062
|
+
contentSource: "tokens",
|
|
1063
|
+
sessionId: mintedSessionId ?? metadataSessionId ?? params.sessionId,
|
|
861
1064
|
usage: metricsChunk?.usage,
|
|
862
1065
|
model: metricsChunk?.model,
|
|
863
1066
|
iterations: metricsChunk?.iterations,
|
|
@@ -868,8 +1071,9 @@ async function invokeAgent(cfg, params) {
|
|
|
868
1071
|
throw new ZihinAgentError("protocol", "Stream ended without a `response` event.");
|
|
869
1072
|
}
|
|
870
1073
|
return {
|
|
871
|
-
content: responseChunk.content
|
|
872
|
-
|
|
1074
|
+
content: responseChunk.content,
|
|
1075
|
+
contentSource: "response",
|
|
1076
|
+
sessionId: mintedSessionId ?? responseChunk.sessionId ?? metadataSessionId ?? params.sessionId,
|
|
873
1077
|
model: metricsChunk?.model,
|
|
874
1078
|
usage: metricsChunk?.usage,
|
|
875
1079
|
sources: responseChunk.sources,
|
|
@@ -880,29 +1084,180 @@ async function invokeAgent(cfg, params) {
|
|
|
880
1084
|
};
|
|
881
1085
|
}
|
|
882
1086
|
|
|
883
|
-
// src/
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
1087
|
+
// src/tasks.ts
|
|
1088
|
+
var MAX_TASK_PAYLOAD_BYTES = 64 * 1024;
|
|
1089
|
+
var SCHEDULE_PAST_TOLERANCE_MS = 6e4;
|
|
1090
|
+
function envelope(json) {
|
|
1091
|
+
const j = rec(json);
|
|
1092
|
+
return { data: rec(j.data ?? j), meta: rec(j.meta) };
|
|
1093
|
+
}
|
|
1094
|
+
function toIso(value, label) {
|
|
1095
|
+
if (value === void 0 || value === null || value === "") return void 0;
|
|
1096
|
+
const d = value instanceof Date ? value : new Date(value);
|
|
1097
|
+
if (Number.isNaN(d.getTime())) {
|
|
1098
|
+
throw new ZihinAgentError("config", `${label} must be a valid ISO 8601 date (got ${JSON.stringify(value)}).`);
|
|
1099
|
+
}
|
|
1100
|
+
return d.toISOString();
|
|
1101
|
+
}
|
|
1102
|
+
function mapTask(raw) {
|
|
1103
|
+
const t = rec(raw);
|
|
1104
|
+
const status = str(t.status);
|
|
1105
|
+
return {
|
|
1106
|
+
id: str(t.id) ?? "",
|
|
1107
|
+
agentId: str(t.agent_id),
|
|
1108
|
+
title: str(t.title),
|
|
1109
|
+
instruction: str(t.instruction),
|
|
1110
|
+
status: status ?? "unknown",
|
|
1111
|
+
// Mesma regra do Caminho B, e de propósito a MESMA função: status desconhecido conta como
|
|
1112
|
+
// terminal (o polling para em vez de girar), status ausente não (não lemos o envelope).
|
|
1113
|
+
terminal: status === void 0 ? false : isTerminalStatus(status),
|
|
1114
|
+
scheduledFor: str(t.scheduled_for),
|
|
1115
|
+
deadlineAt: str(t.deadline_at),
|
|
1116
|
+
parentTaskId: str(t.parent_task_id),
|
|
1117
|
+
rootTaskId: str(t.root_task_id),
|
|
1118
|
+
sessionId: str(t.session_id),
|
|
1119
|
+
createdAt: str(t.created_at),
|
|
1120
|
+
updatedAt: str(t.updated_at),
|
|
1121
|
+
raw
|
|
1122
|
+
};
|
|
1123
|
+
}
|
|
1124
|
+
function mapExecution(raw) {
|
|
1125
|
+
const e = rec(raw);
|
|
1126
|
+
return {
|
|
1127
|
+
id: str(e.id) ?? "",
|
|
1128
|
+
status: str(e.status) ?? "unknown",
|
|
1129
|
+
exitReason: str(e.exit_reason),
|
|
1130
|
+
modelUsed: str(e.model_used),
|
|
1131
|
+
totalCostUsd: num(e.total_cost_usd),
|
|
1132
|
+
executionTimeMs: num(e.execution_time_ms),
|
|
1133
|
+
iterations: num(e.iterations),
|
|
1134
|
+
startedAt: str(e.started_at),
|
|
1135
|
+
finishedAt: str(e.finished_at),
|
|
1136
|
+
raw
|
|
1137
|
+
};
|
|
1138
|
+
}
|
|
1139
|
+
async function delegateTask(cfg, params) {
|
|
1140
|
+
if (!params.agentId) throw new ZihinAgentError("config", "agentId is required.");
|
|
1141
|
+
if (!params.instruction) throw new ZihinAgentError("config", "instruction is required.");
|
|
1142
|
+
const scheduledFor = toIso(params.scheduledFor, "scheduledFor");
|
|
1143
|
+
const deadlineAt = toIso(params.deadlineAt, "deadlineAt");
|
|
1144
|
+
if (scheduledFor && Date.parse(scheduledFor) < Date.now() - SCHEDULE_PAST_TOLERANCE_MS) {
|
|
1145
|
+
throw new ZihinAgentError("config", "scheduledFor is in the past.");
|
|
1146
|
+
}
|
|
1147
|
+
if (deadlineAt && Date.parse(deadlineAt) <= Date.now() - SCHEDULE_PAST_TOLERANCE_MS) {
|
|
1148
|
+
throw new ZihinAgentError("config", "deadlineAt must be in the future.");
|
|
1149
|
+
}
|
|
1150
|
+
if (scheduledFor && deadlineAt && Date.parse(deadlineAt) <= Date.parse(scheduledFor)) {
|
|
1151
|
+
throw new ZihinAgentError("config", "deadlineAt must be after scheduledFor.");
|
|
1152
|
+
}
|
|
1153
|
+
if (params.payload !== void 0) {
|
|
1154
|
+
if (typeof params.payload !== "object" || params.payload === null || Array.isArray(params.payload)) {
|
|
1155
|
+
throw new ZihinAgentError("config", "payload must be an object.");
|
|
1156
|
+
}
|
|
1157
|
+
const bytes = toJsonBody(params.payload, "payload").length;
|
|
1158
|
+
if (bytes > MAX_TASK_PAYLOAD_BYTES) {
|
|
1159
|
+
throw new ZihinAgentError(
|
|
1160
|
+
"config",
|
|
1161
|
+
`payload exceeds ${MAX_TASK_PAYLOAD_BYTES / 1024}KB (got ${Math.ceil(bytes / 1024)}KB).`
|
|
1162
|
+
);
|
|
1163
|
+
}
|
|
1164
|
+
}
|
|
1165
|
+
const body = {
|
|
1166
|
+
agent_id: params.agentId,
|
|
1167
|
+
instruction: params.instruction
|
|
1168
|
+
};
|
|
1169
|
+
if (params.title !== void 0) body.title = params.title;
|
|
1170
|
+
if (params.payload !== void 0) body.payload = params.payload;
|
|
1171
|
+
if (scheduledFor) body.scheduled_for = scheduledFor;
|
|
1172
|
+
if (deadlineAt) body.deadline_at = deadlineAt;
|
|
1173
|
+
if (params.retryPolicy !== void 0) body.retry_policy = params.retryPolicy;
|
|
1174
|
+
if (params.notify !== void 0) body.notify = params.notify;
|
|
1175
|
+
if (params.parentTaskId) body.parent_task_id = params.parentTaskId;
|
|
1176
|
+
const json = await requestJson(cfg, {
|
|
1177
|
+
method: "POST",
|
|
1178
|
+
path: "/api/v1/tasks",
|
|
1179
|
+
body,
|
|
1180
|
+
// Criar uma task E dispará-la é efeito colateral: nunca repetir em erro de rede.
|
|
1181
|
+
policy: "side-effecting",
|
|
1182
|
+
signal: params.signal
|
|
896
1183
|
});
|
|
897
|
-
|
|
898
|
-
|
|
1184
|
+
const { data } = envelope(json);
|
|
1185
|
+
const task = mapTask(data.task ?? data);
|
|
1186
|
+
if (!task.id) {
|
|
1187
|
+
throw new ZihinAgentError("protocol", "delegateTask: response has no task id.");
|
|
899
1188
|
}
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
1189
|
+
return task;
|
|
1190
|
+
}
|
|
1191
|
+
async function listTasks(cfg, params = {}) {
|
|
1192
|
+
const qs = new URLSearchParams();
|
|
1193
|
+
if (params.live) qs.set("live", "true");
|
|
1194
|
+
if (params.status) qs.set("status", params.status);
|
|
1195
|
+
if (params.agentId) qs.set("agent_id", params.agentId);
|
|
1196
|
+
if (params.rootTaskId) qs.set("root_task_id", params.rootTaskId);
|
|
1197
|
+
if (params.limit !== void 0) qs.set("limit", String(params.limit));
|
|
1198
|
+
if (params.offset !== void 0) qs.set("offset", String(params.offset));
|
|
1199
|
+
const query = qs.toString();
|
|
1200
|
+
const json = await requestJson(cfg, {
|
|
1201
|
+
method: "GET",
|
|
1202
|
+
path: `/api/v1/tasks${query ? `?${query}` : ""}`,
|
|
1203
|
+
policy: "idempotent",
|
|
1204
|
+
signal: params.signal
|
|
1205
|
+
});
|
|
1206
|
+
const { data, meta } = envelope(json);
|
|
1207
|
+
const items = Array.isArray(data.items) ? data.items : [];
|
|
1208
|
+
return {
|
|
1209
|
+
items: items.map(mapTask),
|
|
1210
|
+
// ⚠️ `total` vive em `meta`, não no topo — o envelope novo aninhou os campos de paginação.
|
|
1211
|
+
total: num(meta.total),
|
|
1212
|
+
raw: json
|
|
1213
|
+
};
|
|
1214
|
+
}
|
|
1215
|
+
async function getTask(cfg, params) {
|
|
1216
|
+
if (!params.taskId) throw new ZihinAgentError("config", "taskId is required.");
|
|
1217
|
+
const json = await requestJson(cfg, {
|
|
1218
|
+
method: "GET",
|
|
1219
|
+
path: `/api/v1/tasks/${encodeURIComponent(params.taskId)}`,
|
|
1220
|
+
policy: "idempotent",
|
|
1221
|
+
signal: params.signal
|
|
1222
|
+
});
|
|
1223
|
+
const { data } = envelope(json);
|
|
1224
|
+
const taskRaw = data.task ?? (str(rec(data).id) ? data : void 0);
|
|
1225
|
+
if (!taskRaw || !str(rec(taskRaw).id)) {
|
|
1226
|
+
throw new ZihinAgentError("protocol", "getTask: response has no task.");
|
|
904
1227
|
}
|
|
1228
|
+
const executions = Array.isArray(data.executions) ? data.executions : [];
|
|
1229
|
+
const children = Array.isArray(data.children) ? data.children : [];
|
|
1230
|
+
return {
|
|
1231
|
+
task: mapTask(taskRaw),
|
|
1232
|
+
executions: executions.map(mapExecution),
|
|
1233
|
+
children: children.map(mapTask),
|
|
1234
|
+
approvals: Array.isArray(data.approvals) ? data.approvals : [],
|
|
1235
|
+
raw: json
|
|
1236
|
+
};
|
|
905
1237
|
}
|
|
1238
|
+
async function cancelTask(cfg, params) {
|
|
1239
|
+
if (!params.taskId) throw new ZihinAgentError("config", "taskId is required.");
|
|
1240
|
+
const json = await requestJson(cfg, {
|
|
1241
|
+
method: "POST",
|
|
1242
|
+
path: `/api/v1/tasks/${encodeURIComponent(params.taskId)}/cancel`,
|
|
1243
|
+
body: params.reason ? { reason: params.reason } : {},
|
|
1244
|
+
// Cancelar é side-effecting; repetir num socket caído bateria em 409 e confundiria o chamador.
|
|
1245
|
+
policy: "side-effecting",
|
|
1246
|
+
signal: params.signal
|
|
1247
|
+
});
|
|
1248
|
+
const { data } = envelope(json);
|
|
1249
|
+
const taskRaw = data.task ?? (str(rec(data).id) ? data : void 0);
|
|
1250
|
+
if (!taskRaw || !str(rec(taskRaw).id)) {
|
|
1251
|
+
throw new ZihinAgentError("protocol", "cancelTask: response has no task.");
|
|
1252
|
+
}
|
|
1253
|
+
return {
|
|
1254
|
+
task: mapTask(taskRaw),
|
|
1255
|
+
aborted: data.aborted === true,
|
|
1256
|
+
raw: json
|
|
1257
|
+
};
|
|
1258
|
+
}
|
|
1259
|
+
|
|
1260
|
+
// src/triggers.ts
|
|
906
1261
|
function queryExtractionBody(extraction, queryField) {
|
|
907
1262
|
if (!extraction) return { mode: "field", field: queryField ?? "message" };
|
|
908
1263
|
switch (extraction.mode) {
|
|
@@ -935,11 +1290,18 @@ async function createTrigger(cfg, params) {
|
|
|
935
1290
|
throw new ZihinAgentError("config", `Invalid callbackUrl: ${cb.error}`);
|
|
936
1291
|
}
|
|
937
1292
|
}
|
|
1293
|
+
if (params.responseTransform && !params.callbackUrl) {
|
|
1294
|
+
throw new ZihinAgentError(
|
|
1295
|
+
"config",
|
|
1296
|
+
"responseTransform requires callbackUrl: the backend only reads it from trigger_config.execution.callback.response_transform, and a callback without a url is rejected."
|
|
1297
|
+
);
|
|
1298
|
+
}
|
|
938
1299
|
const execution = { mode };
|
|
939
1300
|
if (params.callbackUrl) {
|
|
940
1301
|
execution.callback = {
|
|
941
1302
|
url: params.callbackUrl,
|
|
942
|
-
method: params.callbackMethod ?? "POST"
|
|
1303
|
+
method: params.callbackMethod ?? "POST",
|
|
1304
|
+
...params.responseTransform ? { response_transform: { mode: params.responseTransform } } : {}
|
|
943
1305
|
};
|
|
944
1306
|
}
|
|
945
1307
|
const body = {
|
|
@@ -969,8 +1331,8 @@ async function dispatchAgent(cfg, params) {
|
|
|
969
1331
|
method: "POST",
|
|
970
1332
|
url: `${cfg.baseUrl}/api/triggers/webhook/${encodeURIComponent(params.triggerId)}`,
|
|
971
1333
|
headers: buildHeaders(cfg.apiKey),
|
|
972
|
-
body:
|
|
973
|
-
timeoutMs: cfg
|
|
1334
|
+
body: toJsonBody(params.payload ?? {}, "payload"),
|
|
1335
|
+
timeoutMs: resolveTimeout(cfg, "dispatch"),
|
|
974
1336
|
maxRetries: cfg.maxRetries,
|
|
975
1337
|
policy: "side-effecting",
|
|
976
1338
|
// S1: nunca duplica disparo por erro de rede
|
|
@@ -983,12 +1345,29 @@ async function dispatchAgent(cfg, params) {
|
|
|
983
1345
|
}
|
|
984
1346
|
const json = parseBody(res.text);
|
|
985
1347
|
const j = rec(json);
|
|
1348
|
+
if (j.success === false) {
|
|
1349
|
+
const detail = sanitizeApiError(str(j.error) ?? "").message;
|
|
1350
|
+
throw new ZihinAgentError("server", detail || "The agent run failed.", {
|
|
1351
|
+
status: res.status,
|
|
1352
|
+
endpoint: res.endpoint,
|
|
1353
|
+
attempts: res.attempts,
|
|
1354
|
+
executionId: str(j.execution_id),
|
|
1355
|
+
// Não é transitório: o turno rodou e falhou. Repetir gastaria outra execução.
|
|
1356
|
+
retryable: false
|
|
1357
|
+
});
|
|
1358
|
+
}
|
|
986
1359
|
return {
|
|
987
1360
|
accepted: true,
|
|
988
1361
|
triggerId: str(j.trigger_id) ?? params.triggerId,
|
|
1362
|
+
agentId: str(j.agent_id),
|
|
989
1363
|
executionId: str(j.execution_id),
|
|
990
1364
|
agentResponse: str(j.agent_response),
|
|
991
1365
|
usage: parseUsage(j.usage),
|
|
1366
|
+
iterations: num(j.iterations),
|
|
1367
|
+
latencyMs: num(j.latency_ms),
|
|
1368
|
+
// Qual texto o BE extraiu do payload — é o campo que denuncia um `query_extraction` mal
|
|
1369
|
+
// configurado (mensagem vazia, ou o payload inteiro serializado virando a pergunta).
|
|
1370
|
+
queryExtracted: str(j.query_extracted),
|
|
992
1371
|
raw: json
|
|
993
1372
|
};
|
|
994
1373
|
}
|
|
@@ -1044,7 +1423,7 @@ async function updateTrigger(cfg, params) {
|
|
|
1044
1423
|
if (params.name !== void 0) body.name = params.name;
|
|
1045
1424
|
if (params.description !== void 0) body.description = params.description;
|
|
1046
1425
|
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;
|
|
1426
|
+
const touchesConfig = params.queryExtraction !== void 0 || params.callbackUrl !== void 0 || params.callbackMethod !== void 0 || params.responseTransform !== void 0 || params.mode !== void 0;
|
|
1048
1427
|
if (touchesConfig) {
|
|
1049
1428
|
const path = `/api/triggers/${encodeURIComponent(params.triggerId)}`;
|
|
1050
1429
|
const currentJson = rec(
|
|
@@ -1062,22 +1441,34 @@ async function updateTrigger(cfg, params) {
|
|
|
1062
1441
|
if (params.queryExtraction !== void 0) {
|
|
1063
1442
|
merged.query_extraction = queryExtractionBody(params.queryExtraction, void 0);
|
|
1064
1443
|
}
|
|
1065
|
-
|
|
1444
|
+
const touchesCallback = params.callbackUrl !== void 0 || params.callbackMethod !== void 0 || params.responseTransform !== void 0;
|
|
1445
|
+
if (params.mode !== void 0 || touchesCallback) {
|
|
1066
1446
|
const execution = rec(config.execution);
|
|
1067
1447
|
const callback = rec(execution.callback);
|
|
1068
1448
|
const newExecution = { ...execution };
|
|
1069
1449
|
if (params.mode !== void 0) newExecution.mode = params.mode;
|
|
1070
|
-
if (
|
|
1450
|
+
if (touchesCallback) {
|
|
1071
1451
|
const url = params.callbackUrl ?? str(callback.url);
|
|
1072
1452
|
if (!url) {
|
|
1073
1453
|
throw new ZihinAgentError(
|
|
1074
1454
|
"config",
|
|
1075
|
-
"callbackUrl is required: this trigger has no callback configured yet."
|
|
1455
|
+
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
1456
|
);
|
|
1077
1457
|
}
|
|
1078
1458
|
newExecution.callback = {
|
|
1459
|
+
// Preserva o callback atual: trocar só a URL não pode apagar auth/headers/
|
|
1460
|
+
// body_template/content_type/response_transform. Antes este objeto era
|
|
1461
|
+
// SUBSTITUÍDO por { url, method } — a mesma perda silenciosa que o merge de
|
|
1462
|
+
// trigger_config acima existe para evitar, um nível mais fundo.
|
|
1463
|
+
...callback,
|
|
1079
1464
|
url,
|
|
1080
|
-
method: params.callbackMethod ?? str(callback.method) ?? "POST"
|
|
1465
|
+
method: params.callbackMethod ?? str(callback.method) ?? "POST",
|
|
1466
|
+
...params.responseTransform ? {
|
|
1467
|
+
response_transform: {
|
|
1468
|
+
...rec(callback.response_transform),
|
|
1469
|
+
mode: params.responseTransform
|
|
1470
|
+
}
|
|
1471
|
+
} : {}
|
|
1081
1472
|
};
|
|
1082
1473
|
}
|
|
1083
1474
|
if (newExecution.mode === "async" && !rec(newExecution.callback).url) {
|
|
@@ -1106,7 +1497,7 @@ async function deleteTrigger(cfg, params) {
|
|
|
1106
1497
|
method: "DELETE",
|
|
1107
1498
|
url: `${cfg.baseUrl}/api/triggers/${encodeURIComponent(params.triggerId)}`,
|
|
1108
1499
|
headers: buildHeaders(cfg.apiKey),
|
|
1109
|
-
timeoutMs: cfg
|
|
1500
|
+
timeoutMs: resolveTimeout(cfg, "short"),
|
|
1110
1501
|
maxRetries: cfg.maxRetries,
|
|
1111
1502
|
policy: "idempotent",
|
|
1112
1503
|
// DELETE é idempotente
|
|
@@ -1143,9 +1534,11 @@ async function getExecution(cfg, params) {
|
|
|
1143
1534
|
const data = rec(json.data);
|
|
1144
1535
|
const exec = rec(data.execution ?? json.execution);
|
|
1145
1536
|
const metrics = rec(data.metrics ?? json.metrics);
|
|
1537
|
+
const rawStatus = str(exec.status);
|
|
1146
1538
|
return {
|
|
1147
1539
|
executionId: str(exec.id) ?? params.executionId,
|
|
1148
|
-
status:
|
|
1540
|
+
status: rawStatus ?? "unknown",
|
|
1541
|
+
terminal: rawStatus === void 0 ? false : isTerminalStatus(rawStatus),
|
|
1149
1542
|
startedAt: str(exec.started_at),
|
|
1150
1543
|
finishedAt: str(exec.finished_at),
|
|
1151
1544
|
executionTimeMs: num(exec.execution_time_ms),
|
|
@@ -1226,6 +1619,26 @@ var ZihinAgentClient = class {
|
|
|
1226
1619
|
getExecution(params) {
|
|
1227
1620
|
return this.guard(getExecution(this.cfg, params));
|
|
1228
1621
|
}
|
|
1622
|
+
// ---- Caminho C: tasks (delegação assíncrona durável) ----
|
|
1623
|
+
/**
|
|
1624
|
+
* Delega uma tarefa a um agente (requer key owner/admin). Sem `scheduledFor`, o BE dispara na
|
|
1625
|
+
* hora fora do request e devolve só o handle — acompanhe com `getTask`.
|
|
1626
|
+
*/
|
|
1627
|
+
delegateTask(params) {
|
|
1628
|
+
return this.guard(delegateTask(this.cfg, params));
|
|
1629
|
+
}
|
|
1630
|
+
/** Board de tasks do tenant (requer key owner/admin/editor). */
|
|
1631
|
+
listTasks(params = {}) {
|
|
1632
|
+
return this.guard(listTasks(this.cfg, params));
|
|
1633
|
+
}
|
|
1634
|
+
/** Dossiê de uma task: task + execuções + filhos + aprovações. */
|
|
1635
|
+
getTask(params) {
|
|
1636
|
+
return this.guard(getTask(this.cfg, params));
|
|
1637
|
+
}
|
|
1638
|
+
/** Cancela uma task (requer key owner/admin); derruba o turno em voo, se houver. */
|
|
1639
|
+
cancelTask(params) {
|
|
1640
|
+
return this.guard(cancelTask(this.cfg, params));
|
|
1641
|
+
}
|
|
1229
1642
|
// ---- Auxiliar ----
|
|
1230
1643
|
/** Lista os agentes do tenant (dropdown). */
|
|
1231
1644
|
listAgents(opts = {}) {
|
|
@@ -1264,10 +1677,16 @@ function signRequest(payload, secretKey, timestamp) {
|
|
|
1264
1677
|
};
|
|
1265
1678
|
}
|
|
1266
1679
|
export {
|
|
1680
|
+
DEADLINE_GRACE_MS,
|
|
1267
1681
|
DEFAULT_LIMITS,
|
|
1682
|
+
MAX_TASK_PAYLOAD_BYTES,
|
|
1683
|
+
OPERATION_TIMEOUT_MS,
|
|
1268
1684
|
ZihinAgentClient,
|
|
1269
1685
|
ZihinAgentError,
|
|
1686
|
+
classifyStreamErrorKind,
|
|
1270
1687
|
createClient,
|
|
1688
|
+
errorFromStreamChunk,
|
|
1689
|
+
isCommercialBlock,
|
|
1271
1690
|
sanitizeApiError,
|
|
1272
1691
|
signRequest,
|
|
1273
1692
|
validateApiKeyFormat,
|