@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.js
CHANGED
|
@@ -20,10 +20,16 @@ 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,
|
|
29
|
+
classifyStreamErrorKind: () => classifyStreamErrorKind,
|
|
26
30
|
createClient: () => createClient,
|
|
31
|
+
errorFromStreamChunk: () => errorFromStreamChunk,
|
|
32
|
+
isCommercialBlock: () => isCommercialBlock,
|
|
27
33
|
sanitizeApiError: () => sanitizeApiError,
|
|
28
34
|
signRequest: () => signRequest,
|
|
29
35
|
validateApiKeyFormat: () => validateApiKeyFormat,
|
|
@@ -56,6 +62,21 @@ function parseUsage(usageRaw) {
|
|
|
56
62
|
costUsd: num(u.cost_usd)
|
|
57
63
|
};
|
|
58
64
|
}
|
|
65
|
+
var RUNNING_STATUSES = /* @__PURE__ */ new Set([
|
|
66
|
+
"running",
|
|
67
|
+
"pending",
|
|
68
|
+
"queued",
|
|
69
|
+
"processing",
|
|
70
|
+
"in_progress",
|
|
71
|
+
"waiting",
|
|
72
|
+
"retrying",
|
|
73
|
+
"scheduled",
|
|
74
|
+
"awaiting_approval"
|
|
75
|
+
// HITL: turno pausado esperando decisão humana não terminou
|
|
76
|
+
]);
|
|
77
|
+
function isTerminalStatus(status) {
|
|
78
|
+
return !RUNNING_STATUSES.has(status.toLowerCase().replace(/-/g, "_"));
|
|
79
|
+
}
|
|
59
80
|
|
|
60
81
|
// src/security.ts
|
|
61
82
|
var PRIVATE_IP_PATTERNS = [
|
|
@@ -238,14 +259,19 @@ var SENSITIVE_PATTERNS = [
|
|
|
238
259
|
var ERROR_CODE_MAP = {
|
|
239
260
|
RATE_LIMIT_EXCEEDED: "Rate limit exceeded. Please wait before retrying.",
|
|
240
261
|
UNAUTHORIZED: "Authentication failed. Please check your API credentials.",
|
|
241
|
-
FORBIDDEN: "This API key
|
|
262
|
+
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
263
|
INVALID_MODEL: "The specified model is not available.",
|
|
243
264
|
INTERNAL_ERROR: "An internal error occurred. Please try again later.",
|
|
244
265
|
TIMEOUT: "Request timed out. Try increasing the timeout.",
|
|
245
266
|
INVALID_REQUEST: "The request was malformed. Please check your input.",
|
|
246
267
|
INSUFFICIENT_CREDITS: "Insufficient credits. Please check your account balance.",
|
|
247
268
|
CONTEXT_LENGTH_EXCEEDED: "The input exceeds the model context length limit.",
|
|
248
|
-
AGENT_NOT_FOUND: "Agent not found or not active for this tenant."
|
|
269
|
+
AGENT_NOT_FOUND: "Agent not found or not active for this tenant.",
|
|
270
|
+
// Bloqueios comerciais. As mensagens do BE vêm em português e estes shells são em inglês;
|
|
271
|
+
// além disso cada caso tem uma ação DIFERENTE, e o texto precisa dizer qual.
|
|
272
|
+
TRIAL_EXPIRED: "Your Zihin trial has ended. Upgrade the plan to keep invoking agents \u2014 the API key is fine.",
|
|
273
|
+
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.",
|
|
274
|
+
NO_ACTIVE_SUBSCRIPTION: "This tenant has no active Zihin subscription. Activate a plan to invoke agents \u2014 the API key is fine."
|
|
249
275
|
};
|
|
250
276
|
function sanitizeApiError(error) {
|
|
251
277
|
let message = "An unexpected error occurred";
|
|
@@ -258,8 +284,10 @@ function sanitizeApiError(error) {
|
|
|
258
284
|
const codeRaw = nested?.code ?? (typeof err.error === "string" ? err.error : err.code);
|
|
259
285
|
code = typeof codeRaw === "string" ? codeRaw : void 0;
|
|
260
286
|
const rawMessage = nested?.message ?? err.message;
|
|
261
|
-
|
|
262
|
-
|
|
287
|
+
const reason = typeof err.reason === "string" ? err.reason : void 0;
|
|
288
|
+
const effectiveCode = code === "QUOTA_EXCEEDED" && reason === "no_active_subscription" ? "NO_ACTIVE_SUBSCRIPTION" : code;
|
|
289
|
+
if (effectiveCode && ERROR_CODE_MAP[effectiveCode]) {
|
|
290
|
+
message = ERROR_CODE_MAP[effectiveCode];
|
|
263
291
|
} else if (typeof rawMessage === "string" && rawMessage) {
|
|
264
292
|
message = rawMessage;
|
|
265
293
|
}
|
|
@@ -308,6 +336,7 @@ var ZihinAgentError = class _ZihinAgentError extends Error {
|
|
|
308
336
|
this.status = opts.status;
|
|
309
337
|
this.code = opts.code;
|
|
310
338
|
this.requestId = opts.requestId;
|
|
339
|
+
this.executionId = opts.executionId;
|
|
311
340
|
this.attempts = opts.attempts;
|
|
312
341
|
this.endpoint = opts.endpoint;
|
|
313
342
|
this.retryable = opts.retryable ?? (kind === "rate_limit" || kind === "server" || kind === "network");
|
|
@@ -319,25 +348,65 @@ function buildMessage(base, opts) {
|
|
|
319
348
|
if (opts.endpoint) s += ` [${opts.endpoint}]`;
|
|
320
349
|
if (opts.attempts && opts.attempts > 1) s += ` (after ${opts.attempts} attempts)`;
|
|
321
350
|
if (opts.requestId) s += ` (request_id: ${opts.requestId})`;
|
|
351
|
+
if (opts.executionId) s += ` (execution_id: ${opts.executionId})`;
|
|
322
352
|
return s;
|
|
323
353
|
}
|
|
354
|
+
var COMMERCIAL_BLOCK_CODES = /* @__PURE__ */ new Set(["TRIAL_EXPIRED", "QUOTA_EXCEEDED", "NO_ACTIVE_SUBSCRIPTION"]);
|
|
355
|
+
function errorCodeOf(body) {
|
|
356
|
+
const b = rec(body);
|
|
357
|
+
return str(b.error) ?? str(rec(b.error).code) ?? str(b.code);
|
|
358
|
+
}
|
|
359
|
+
function isCommercialBlock(body) {
|
|
360
|
+
const code = errorCodeOf(body);
|
|
361
|
+
return code !== void 0 && COMMERCIAL_BLOCK_CODES.has(code);
|
|
362
|
+
}
|
|
363
|
+
function commercialKind(body) {
|
|
364
|
+
const code = errorCodeOf(body);
|
|
365
|
+
if (code === "TRIAL_EXPIRED") return "trial_expired";
|
|
366
|
+
if (code === "NO_ACTIVE_SUBSCRIPTION") return "no_subscription";
|
|
367
|
+
if (code === "QUOTA_EXCEEDED") {
|
|
368
|
+
return str(rec(body).reason) === "no_active_subscription" ? "no_subscription" : "quota_exceeded";
|
|
369
|
+
}
|
|
370
|
+
return void 0;
|
|
371
|
+
}
|
|
324
372
|
function extractRequestId(body) {
|
|
325
373
|
const b = rec(body);
|
|
326
|
-
return str(b.request_id) ?? str(b.requestId) ?? str(rec(b.error).request_id);
|
|
374
|
+
return str(b.request_id) ?? str(b.requestId) ?? str(rec(b.error).request_id) ?? str(rec(b.meta).request_id);
|
|
327
375
|
}
|
|
328
376
|
function errorFromResponse(status, body, meta = {}) {
|
|
329
377
|
const sanitized = sanitizeApiError(body);
|
|
330
378
|
const message = sanitized.message;
|
|
331
379
|
const code = sanitized.code;
|
|
332
|
-
const base = {
|
|
380
|
+
const base = {
|
|
381
|
+
status,
|
|
382
|
+
code,
|
|
383
|
+
requestId: extractRequestId(body),
|
|
384
|
+
executionId: str(rec(body).execution_id),
|
|
385
|
+
...meta
|
|
386
|
+
};
|
|
387
|
+
const commercial = commercialKind(body);
|
|
388
|
+
if (commercial) {
|
|
389
|
+
return new ZihinAgentError(commercial, message || "Request blocked by your plan.", {
|
|
390
|
+
...base,
|
|
391
|
+
retryable: false
|
|
392
|
+
});
|
|
393
|
+
}
|
|
333
394
|
if (status === 401) return new ZihinAgentError("auth", message || "Authentication failed.", base);
|
|
334
395
|
if (status === 403)
|
|
335
396
|
return new ZihinAgentError(
|
|
336
397
|
"auth",
|
|
337
|
-
|
|
398
|
+
// Não enumerar papéis aqui: o papel exigido varia por rota (triggers aceitam editor,
|
|
399
|
+
// mas delegar/cancelar uma task exige admin/owner). A frase antiga mandava o usuário
|
|
400
|
+
// para o papel errado em metade da API.
|
|
401
|
+
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
402
|
base
|
|
339
403
|
);
|
|
340
404
|
if (status === 404) return new ZihinAgentError("not_found", message || "Resource not found.", base);
|
|
405
|
+
if (status === 408)
|
|
406
|
+
return new ZihinAgentError("timeout", message || "The server timed out handling the request.", {
|
|
407
|
+
...base,
|
|
408
|
+
retryable: true
|
|
409
|
+
});
|
|
341
410
|
if (status === 429)
|
|
342
411
|
return new ZihinAgentError("rate_limit", message || "Rate limit exceeded.", { ...base, retryable: true });
|
|
343
412
|
if (status >= 500)
|
|
@@ -367,7 +436,25 @@ function errorFromThrow(err, timeoutMs, meta = {}) {
|
|
|
367
436
|
|
|
368
437
|
// src/http.ts
|
|
369
438
|
var DEFAULT_BASE_URL = "https://llm.zihin.ai";
|
|
370
|
-
var
|
|
439
|
+
var OPERATION_TIMEOUT_MS = Object.freeze({
|
|
440
|
+
stream: 2e5,
|
|
441
|
+
dispatch: 26e4,
|
|
442
|
+
short: 35e3
|
|
443
|
+
});
|
|
444
|
+
var DEADLINE_GRACE_MS = 1e4;
|
|
445
|
+
var RETRY_OVERHEAD_MS = 6e4;
|
|
446
|
+
var MIN_TIMEOUT_MS = 1e3;
|
|
447
|
+
var MAX_TIMEOUT_MS = 9e5;
|
|
448
|
+
function normalizeTimeoutMs(value, label = "timeoutMs") {
|
|
449
|
+
const n = typeof value === "number" ? value : Number(value);
|
|
450
|
+
if (!Number.isFinite(n) || n <= 0) {
|
|
451
|
+
throw new ZihinAgentError(
|
|
452
|
+
"config",
|
|
453
|
+
`${label} must be a positive number of milliseconds (got ${JSON.stringify(value)}).`
|
|
454
|
+
);
|
|
455
|
+
}
|
|
456
|
+
return Math.min(Math.max(n, MIN_TIMEOUT_MS), MAX_TIMEOUT_MS);
|
|
457
|
+
}
|
|
371
458
|
var DEFAULT_MAX_RETRIES = 2;
|
|
372
459
|
var DEFAULT_MAX_EVENT_BYTES = 1 * 1024 * 1024;
|
|
373
460
|
var DEFAULT_MAX_TOTAL_BYTES = 64 * 1024 * 1024;
|
|
@@ -400,7 +487,7 @@ function resolveConfig(config) {
|
|
|
400
487
|
return {
|
|
401
488
|
apiKey: config.apiKey.trim(),
|
|
402
489
|
baseUrl: urlCheck.sanitizedUrl || DEFAULT_BASE_URL,
|
|
403
|
-
timeoutMs: config.timeoutMs
|
|
490
|
+
timeoutMs: config.timeoutMs === void 0 || config.timeoutMs === null ? void 0 : normalizeTimeoutMs(config.timeoutMs, "timeoutMs"),
|
|
404
491
|
maxRetries: config.maxRetries ?? DEFAULT_MAX_RETRIES,
|
|
405
492
|
maxStreamEventBytes: config.maxStreamEventBytes ?? DEFAULT_MAX_EVENT_BYTES,
|
|
406
493
|
maxStreamTotalBytes: config.maxStreamTotalBytes ?? DEFAULT_MAX_TOTAL_BYTES,
|
|
@@ -409,6 +496,9 @@ function resolveConfig(config) {
|
|
|
409
496
|
onError: config.onError
|
|
410
497
|
};
|
|
411
498
|
}
|
|
499
|
+
function resolveTimeout(cfg, operation) {
|
|
500
|
+
return cfg.timeoutMs ?? OPERATION_TIMEOUT_MS[operation];
|
|
501
|
+
}
|
|
412
502
|
function endpointLabel(method, url) {
|
|
413
503
|
try {
|
|
414
504
|
const u = new URL(url);
|
|
@@ -482,12 +572,27 @@ function createDeadline(timeoutMs, callerSignal) {
|
|
|
482
572
|
};
|
|
483
573
|
}
|
|
484
574
|
function shouldRetryStatus(status, policy) {
|
|
485
|
-
if (policy === "idempotent") return status >= 500 || status === 429;
|
|
575
|
+
if (policy === "idempotent") return status >= 500 || status === 429 || status === 408;
|
|
486
576
|
return status === 429 || status === 503;
|
|
487
577
|
}
|
|
578
|
+
function budgetTracker(args) {
|
|
579
|
+
const startedAt = Date.now();
|
|
580
|
+
const reported = args.reportedTimeoutMs ?? args.timeoutMs;
|
|
581
|
+
const overheadCap = Math.min(args.timeoutMs, RETRY_OVERHEAD_MS);
|
|
582
|
+
return {
|
|
583
|
+
reported,
|
|
584
|
+
/** Quanto ainda pode ser gasto com tentativas falhas + backoff. */
|
|
585
|
+
overheadLeft: () => overheadCap - (Date.now() - startedAt),
|
|
586
|
+
expired: (attempts) => new ZihinAgentError("timeout", `Request timed out after ${reported}ms.`, {
|
|
587
|
+
endpoint: args.endpoint,
|
|
588
|
+
attempts
|
|
589
|
+
})
|
|
590
|
+
};
|
|
591
|
+
}
|
|
488
592
|
async function requestBuffered(args) {
|
|
489
593
|
const endpoint = endpointLabel(args.method, args.url);
|
|
490
594
|
const doFetch = resolveFetch(args.fetchImpl);
|
|
595
|
+
const budget = budgetTracker({ ...args, endpoint });
|
|
491
596
|
let lastError = null;
|
|
492
597
|
for (let attempt = 0; attempt <= args.maxRetries; attempt++) {
|
|
493
598
|
const d = createDeadline(args.timeoutMs, args.callerSignal);
|
|
@@ -502,30 +607,39 @@ async function requestBuffered(args) {
|
|
|
502
607
|
cache: "no-store"
|
|
503
608
|
// evita cache stale (ex.: polling em Next.js/edge)
|
|
504
609
|
});
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
610
|
+
let wait = shouldRetryStatus(res.status, args.policy) && attempt < args.maxRetries ? retryAfterMs(res.headers) ?? backoffMs(attempt) : null;
|
|
611
|
+
let peeked;
|
|
612
|
+
if (wait !== null && res.status === 429) {
|
|
613
|
+
peeked = await res.text();
|
|
614
|
+
if (isCommercialBlock(parseBody(peeked))) wait = null;
|
|
615
|
+
}
|
|
616
|
+
if (wait !== null && budget.overheadLeft() > wait) {
|
|
617
|
+
if (peeked === void 0) {
|
|
618
|
+
try {
|
|
619
|
+
await res.body?.cancel();
|
|
620
|
+
} catch {
|
|
621
|
+
}
|
|
510
622
|
}
|
|
511
623
|
d.dispose();
|
|
512
624
|
args.onRetry?.({ endpoint, attempt: attempt + 1, delayMs: wait, reason: "status", status: res.status });
|
|
513
625
|
await delay(wait, args.callerSignal);
|
|
514
626
|
continue;
|
|
515
627
|
}
|
|
516
|
-
const text = await res.text();
|
|
628
|
+
const text = peeked ?? await res.text();
|
|
517
629
|
d.dispose();
|
|
518
630
|
return { status: res.status, ok: res.ok, text, endpoint, attempts: attempt + 1 };
|
|
519
631
|
} catch (err) {
|
|
520
632
|
d.dispose();
|
|
633
|
+
if (err instanceof ZihinAgentError) throw err;
|
|
521
634
|
const meta = { endpoint, attempts: attempt + 1 };
|
|
522
|
-
if (d.timedOut()) throw
|
|
635
|
+
if (d.timedOut()) throw budget.expired(attempt + 1);
|
|
523
636
|
if (d.abortedByCaller())
|
|
524
637
|
throw new ZihinAgentError("aborted", "Request was aborted by the caller.", meta);
|
|
525
|
-
lastError = errorFromThrow(err,
|
|
638
|
+
lastError = errorFromThrow(err, budget.reported, meta);
|
|
526
639
|
const canRetry = args.policy === "idempotent" && lastError.retryable && attempt < args.maxRetries;
|
|
527
640
|
if (!canRetry) throw lastError;
|
|
528
641
|
const wait = backoffMs(attempt);
|
|
642
|
+
if (budget.overheadLeft() <= wait) throw lastError;
|
|
529
643
|
args.onRetry?.({ endpoint, attempt: attempt + 1, delayMs: wait, reason: "network" });
|
|
530
644
|
await delay(wait, args.callerSignal);
|
|
531
645
|
}
|
|
@@ -535,6 +649,7 @@ async function requestBuffered(args) {
|
|
|
535
649
|
async function openStream(args) {
|
|
536
650
|
const endpoint = endpointLabel("POST", args.url);
|
|
537
651
|
const doFetch = resolveFetch(args.fetchImpl);
|
|
652
|
+
const budget = budgetTracker({ ...args, endpoint });
|
|
538
653
|
for (let attempt = 0; attempt <= args.maxRetries; attempt++) {
|
|
539
654
|
const d = createDeadline(args.timeoutMs, args.callerSignal);
|
|
540
655
|
try {
|
|
@@ -546,8 +661,20 @@ async function openStream(args) {
|
|
|
546
661
|
redirect: "error",
|
|
547
662
|
cache: "no-store"
|
|
548
663
|
});
|
|
549
|
-
|
|
550
|
-
|
|
664
|
+
let wait = shouldRetryStatus(response.status, "side-effecting") && attempt < args.maxRetries ? retryAfterMs(response.headers) ?? backoffMs(attempt) : null;
|
|
665
|
+
if (response.status === 429) {
|
|
666
|
+
const text = await response.text().catch(() => "");
|
|
667
|
+
const body = parseBody(text);
|
|
668
|
+
if (wait === null || isCommercialBlock(body) || budget.overheadLeft() <= wait) {
|
|
669
|
+
d.dispose();
|
|
670
|
+
throw errorFromResponse(response.status, body, { endpoint, attempts: attempt + 1 });
|
|
671
|
+
}
|
|
672
|
+
d.dispose();
|
|
673
|
+
args.onRetry?.({ endpoint, attempt: attempt + 1, delayMs: wait, reason: "status", status: 429 });
|
|
674
|
+
await delay(wait, args.callerSignal);
|
|
675
|
+
continue;
|
|
676
|
+
}
|
|
677
|
+
if (wait !== null && budget.overheadLeft() > wait) {
|
|
551
678
|
try {
|
|
552
679
|
await response.body?.cancel();
|
|
553
680
|
} catch {
|
|
@@ -566,15 +693,51 @@ async function openStream(args) {
|
|
|
566
693
|
return { response, deadline: d, endpoint };
|
|
567
694
|
} catch (err) {
|
|
568
695
|
d.dispose();
|
|
696
|
+
if (err instanceof ZihinAgentError) throw err;
|
|
569
697
|
const meta = { endpoint, attempts: attempt + 1 };
|
|
570
|
-
if (d.timedOut()) throw
|
|
698
|
+
if (d.timedOut()) throw budget.expired(attempt + 1);
|
|
571
699
|
if (d.abortedByCaller())
|
|
572
700
|
throw new ZihinAgentError("aborted", "Request was aborted by the caller.", meta);
|
|
573
|
-
throw errorFromThrow(err,
|
|
701
|
+
throw errorFromThrow(err, budget.reported, meta);
|
|
574
702
|
}
|
|
575
703
|
}
|
|
576
704
|
throw new ZihinAgentError("unknown", "Max retries exceeded.", { endpoint });
|
|
577
705
|
}
|
|
706
|
+
function toJsonBody(value, label = "body") {
|
|
707
|
+
try {
|
|
708
|
+
return JSON.stringify(value);
|
|
709
|
+
} catch (err) {
|
|
710
|
+
throw new ZihinAgentError(
|
|
711
|
+
"config",
|
|
712
|
+
`${label} is not JSON-serializable: ${err?.message ?? "unknown reason"}.`
|
|
713
|
+
);
|
|
714
|
+
}
|
|
715
|
+
}
|
|
716
|
+
async function requestJson(cfg, args) {
|
|
717
|
+
const res = await requestBuffered({
|
|
718
|
+
method: args.method,
|
|
719
|
+
url: `${cfg.baseUrl}${args.path}`,
|
|
720
|
+
headers: buildHeaders(cfg.apiKey),
|
|
721
|
+
body: args.body !== void 0 ? toJsonBody(args.body) : void 0,
|
|
722
|
+
timeoutMs: resolveTimeout(cfg, args.operation ?? "short"),
|
|
723
|
+
maxRetries: cfg.maxRetries,
|
|
724
|
+
policy: args.policy,
|
|
725
|
+
callerSignal: args.signal,
|
|
726
|
+
fetchImpl: cfg.fetchImpl,
|
|
727
|
+
onRetry: cfg.onRetry
|
|
728
|
+
});
|
|
729
|
+
if (!res.ok) {
|
|
730
|
+
throw errorFromResponse(res.status, parseBody(res.text), {
|
|
731
|
+
endpoint: res.endpoint,
|
|
732
|
+
attempts: res.attempts
|
|
733
|
+
});
|
|
734
|
+
}
|
|
735
|
+
try {
|
|
736
|
+
return JSON.parse(res.text);
|
|
737
|
+
} catch {
|
|
738
|
+
throw new ZihinAgentError("protocol", "Expected JSON response.", { endpoint: res.endpoint });
|
|
739
|
+
}
|
|
740
|
+
}
|
|
578
741
|
function parseBody(text) {
|
|
579
742
|
if (!text) return {};
|
|
580
743
|
try {
|
|
@@ -590,7 +753,7 @@ async function listAgents(cfg, opts = {}) {
|
|
|
590
753
|
method: "GET",
|
|
591
754
|
url: `${cfg.baseUrl}/api/v2/agents`,
|
|
592
755
|
headers: buildHeaders(cfg.apiKey),
|
|
593
|
-
timeoutMs: cfg
|
|
756
|
+
timeoutMs: resolveTimeout(cfg, "short"),
|
|
594
757
|
maxRetries: cfg.maxRetries,
|
|
595
758
|
policy: "idempotent",
|
|
596
759
|
callerSignal: opts.signal,
|
|
@@ -757,6 +920,21 @@ function mapEvent(e) {
|
|
|
757
920
|
return { type: "token", content: str(d.content) ?? "", data: e.data };
|
|
758
921
|
case "warning":
|
|
759
922
|
return { type: "warning", message: str(d.message), data: e.data };
|
|
923
|
+
case "session":
|
|
924
|
+
return { type: "session", sessionId: str(d.session_id), data: e.data };
|
|
925
|
+
case "done":
|
|
926
|
+
return { type: "done", data: e.data };
|
|
927
|
+
case "error": {
|
|
928
|
+
const err = rec(d.error);
|
|
929
|
+
return {
|
|
930
|
+
type: "error",
|
|
931
|
+
code: str(err.code),
|
|
932
|
+
message: str(err.message),
|
|
933
|
+
requestId: str(err.request_id),
|
|
934
|
+
timedOut: typeof err.timed_out === "boolean" ? err.timed_out : void 0,
|
|
935
|
+
data: e.data
|
|
936
|
+
};
|
|
937
|
+
}
|
|
760
938
|
case "response":
|
|
761
939
|
return {
|
|
762
940
|
type: "response",
|
|
@@ -781,7 +959,7 @@ function mapEvent(e) {
|
|
|
781
959
|
return { type: "unknown", event: e.event, data: e.data };
|
|
782
960
|
}
|
|
783
961
|
}
|
|
784
|
-
function buildRequestBody(params) {
|
|
962
|
+
function buildRequestBody(params, normalizedTimeoutMs) {
|
|
785
963
|
const body = {};
|
|
786
964
|
if (params.message !== void 0) body.message = params.message;
|
|
787
965
|
if (params.sessionId) body.session_id = params.sessionId;
|
|
@@ -792,7 +970,7 @@ function buildRequestBody(params) {
|
|
|
792
970
|
const o = {};
|
|
793
971
|
if (params.options.temperature !== void 0) o.temperature = params.options.temperature;
|
|
794
972
|
if (params.options.maxTokens !== void 0) o.max_tokens = params.options.maxTokens;
|
|
795
|
-
if (
|
|
973
|
+
if (normalizedTimeoutMs !== void 0) o.timeout_ms = normalizedTimeoutMs;
|
|
796
974
|
if (params.options.jitMode !== void 0) o.jit_mode = params.options.jitMode;
|
|
797
975
|
if (Object.keys(o).length) body.options = o;
|
|
798
976
|
}
|
|
@@ -823,6 +1001,9 @@ function validateParams(params) {
|
|
|
823
1001
|
throw new ZihinAgentError("config", sizeCheck.error || "Message too large.");
|
|
824
1002
|
}
|
|
825
1003
|
}
|
|
1004
|
+
if (params.options?.timeoutMs !== void 0) {
|
|
1005
|
+
normalizeTimeoutMs(params.options.timeoutMs, "options.timeoutMs");
|
|
1006
|
+
}
|
|
826
1007
|
for (const att of params.attachments ?? []) {
|
|
827
1008
|
const urlCheck = validateBaseUrl(att.url, { allowCustomDomains: true });
|
|
828
1009
|
if (!urlCheck.valid) {
|
|
@@ -832,13 +1013,16 @@ function validateParams(params) {
|
|
|
832
1013
|
}
|
|
833
1014
|
async function* streamAgent(cfg, params) {
|
|
834
1015
|
validateParams(params);
|
|
835
|
-
const
|
|
1016
|
+
const callerTimeoutMs = params.options?.timeoutMs !== void 0 ? normalizeTimeoutMs(params.options.timeoutMs, "options.timeoutMs") : void 0;
|
|
1017
|
+
const timeoutMs = callerTimeoutMs !== void 0 ? callerTimeoutMs + DEADLINE_GRACE_MS : resolveTimeout(cfg, "stream");
|
|
1018
|
+
const reportedMs = callerTimeoutMs ?? timeoutMs;
|
|
836
1019
|
const url = `${cfg.baseUrl}/api/v2/agents/${encodeURIComponent(params.agentId)}/stream`;
|
|
837
1020
|
const { response, deadline, endpoint } = await openStream({
|
|
838
1021
|
url,
|
|
839
1022
|
headers: buildHeaders(cfg.apiKey),
|
|
840
|
-
body:
|
|
1023
|
+
body: toJsonBody(buildRequestBody(params, callerTimeoutMs), "request body"),
|
|
841
1024
|
timeoutMs,
|
|
1025
|
+
reportedTimeoutMs: reportedMs,
|
|
842
1026
|
maxRetries: cfg.maxRetries,
|
|
843
1027
|
callerSignal: params.signal,
|
|
844
1028
|
fetchImpl: cfg.fetchImpl,
|
|
@@ -853,7 +1037,7 @@ async function* streamAgent(cfg, params) {
|
|
|
853
1037
|
}
|
|
854
1038
|
throw errorFromResponse(response.status, parseBody(text), { endpoint });
|
|
855
1039
|
}
|
|
856
|
-
const onAbortError = () => deadline.timedOut() ? new ZihinAgentError("timeout", `Stream timed out after ${
|
|
1040
|
+
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
1041
|
for await (const e of parseSse(response, {
|
|
858
1042
|
signal: params.signal,
|
|
859
1043
|
onAbortError,
|
|
@@ -866,25 +1050,50 @@ async function* streamAgent(cfg, params) {
|
|
|
866
1050
|
deadline.dispose();
|
|
867
1051
|
}
|
|
868
1052
|
}
|
|
1053
|
+
function classifyStreamErrorKind(code, timedOut) {
|
|
1054
|
+
if (timedOut === true) return "timeout";
|
|
1055
|
+
if (timedOut === false) return "server";
|
|
1056
|
+
return /TIMEOUT/i.test(code) ? "timeout" : "server";
|
|
1057
|
+
}
|
|
1058
|
+
function errorFromStreamChunk(chunk) {
|
|
1059
|
+
const code = chunk.code ?? "AGENT_ERROR";
|
|
1060
|
+
const kind = classifyStreamErrorKind(code, chunk.timedOut);
|
|
1061
|
+
return new ZihinAgentError(kind, sanitizeApiError(chunk.message ?? code).message || code, {
|
|
1062
|
+
code,
|
|
1063
|
+
requestId: chunk.requestId,
|
|
1064
|
+
retryable: false
|
|
1065
|
+
});
|
|
1066
|
+
}
|
|
869
1067
|
async function invokeAgent(cfg, params) {
|
|
870
1068
|
const raw = [];
|
|
871
1069
|
const keepRaw = params.includeRaw === true;
|
|
872
1070
|
let responseChunk;
|
|
873
1071
|
let metricsChunk;
|
|
874
1072
|
let metadataSessionId;
|
|
1073
|
+
let mintedSessionId;
|
|
1074
|
+
let cleanEnd = false;
|
|
875
1075
|
let tokenBuffer = "";
|
|
876
1076
|
for await (const chunk of streamAgent(cfg, params)) {
|
|
877
1077
|
if (keepRaw) raw.push(chunk);
|
|
1078
|
+
if (chunk.type === "error") {
|
|
1079
|
+
throw errorFromStreamChunk(chunk);
|
|
1080
|
+
}
|
|
878
1081
|
if (chunk.type === "response") responseChunk = chunk;
|
|
879
1082
|
else if (chunk.type === "metrics") metricsChunk = chunk;
|
|
880
1083
|
else if (chunk.type === "token") tokenBuffer += chunk.content;
|
|
881
1084
|
else if (chunk.type === "metadata" && chunk.sessionId) metadataSessionId = chunk.sessionId;
|
|
1085
|
+
else if (chunk.type === "session" && chunk.sessionId) mintedSessionId = chunk.sessionId;
|
|
1086
|
+
else if (chunk.type === "done") cleanEnd = true;
|
|
882
1087
|
}
|
|
883
1088
|
if (!responseChunk) {
|
|
1089
|
+
if (cleanEnd) {
|
|
1090
|
+
throw new ZihinAgentError("protocol", "Stream ended (done) without a `response` event.");
|
|
1091
|
+
}
|
|
884
1092
|
if (tokenBuffer) {
|
|
885
1093
|
return {
|
|
886
1094
|
content: tokenBuffer,
|
|
887
|
-
|
|
1095
|
+
contentSource: "tokens",
|
|
1096
|
+
sessionId: mintedSessionId ?? metadataSessionId ?? params.sessionId,
|
|
888
1097
|
usage: metricsChunk?.usage,
|
|
889
1098
|
model: metricsChunk?.model,
|
|
890
1099
|
iterations: metricsChunk?.iterations,
|
|
@@ -895,8 +1104,9 @@ async function invokeAgent(cfg, params) {
|
|
|
895
1104
|
throw new ZihinAgentError("protocol", "Stream ended without a `response` event.");
|
|
896
1105
|
}
|
|
897
1106
|
return {
|
|
898
|
-
content: responseChunk.content
|
|
899
|
-
|
|
1107
|
+
content: responseChunk.content,
|
|
1108
|
+
contentSource: "response",
|
|
1109
|
+
sessionId: mintedSessionId ?? responseChunk.sessionId ?? metadataSessionId ?? params.sessionId,
|
|
900
1110
|
model: metricsChunk?.model,
|
|
901
1111
|
usage: metricsChunk?.usage,
|
|
902
1112
|
sources: responseChunk.sources,
|
|
@@ -907,29 +1117,180 @@ async function invokeAgent(cfg, params) {
|
|
|
907
1117
|
};
|
|
908
1118
|
}
|
|
909
1119
|
|
|
910
|
-
// src/
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
1120
|
+
// src/tasks.ts
|
|
1121
|
+
var MAX_TASK_PAYLOAD_BYTES = 64 * 1024;
|
|
1122
|
+
var SCHEDULE_PAST_TOLERANCE_MS = 6e4;
|
|
1123
|
+
function envelope(json) {
|
|
1124
|
+
const j = rec(json);
|
|
1125
|
+
return { data: rec(j.data ?? j), meta: rec(j.meta) };
|
|
1126
|
+
}
|
|
1127
|
+
function toIso(value, label) {
|
|
1128
|
+
if (value === void 0 || value === null || value === "") return void 0;
|
|
1129
|
+
const d = value instanceof Date ? value : new Date(value);
|
|
1130
|
+
if (Number.isNaN(d.getTime())) {
|
|
1131
|
+
throw new ZihinAgentError("config", `${label} must be a valid ISO 8601 date (got ${JSON.stringify(value)}).`);
|
|
1132
|
+
}
|
|
1133
|
+
return d.toISOString();
|
|
1134
|
+
}
|
|
1135
|
+
function mapTask(raw) {
|
|
1136
|
+
const t = rec(raw);
|
|
1137
|
+
const status = str(t.status);
|
|
1138
|
+
return {
|
|
1139
|
+
id: str(t.id) ?? "",
|
|
1140
|
+
agentId: str(t.agent_id),
|
|
1141
|
+
title: str(t.title),
|
|
1142
|
+
instruction: str(t.instruction),
|
|
1143
|
+
status: status ?? "unknown",
|
|
1144
|
+
// Mesma regra do Caminho B, e de propósito a MESMA função: status desconhecido conta como
|
|
1145
|
+
// terminal (o polling para em vez de girar), status ausente não (não lemos o envelope).
|
|
1146
|
+
terminal: status === void 0 ? false : isTerminalStatus(status),
|
|
1147
|
+
scheduledFor: str(t.scheduled_for),
|
|
1148
|
+
deadlineAt: str(t.deadline_at),
|
|
1149
|
+
parentTaskId: str(t.parent_task_id),
|
|
1150
|
+
rootTaskId: str(t.root_task_id),
|
|
1151
|
+
sessionId: str(t.session_id),
|
|
1152
|
+
createdAt: str(t.created_at),
|
|
1153
|
+
updatedAt: str(t.updated_at),
|
|
1154
|
+
raw
|
|
1155
|
+
};
|
|
1156
|
+
}
|
|
1157
|
+
function mapExecution(raw) {
|
|
1158
|
+
const e = rec(raw);
|
|
1159
|
+
return {
|
|
1160
|
+
id: str(e.id) ?? "",
|
|
1161
|
+
status: str(e.status) ?? "unknown",
|
|
1162
|
+
exitReason: str(e.exit_reason),
|
|
1163
|
+
modelUsed: str(e.model_used),
|
|
1164
|
+
totalCostUsd: num(e.total_cost_usd),
|
|
1165
|
+
executionTimeMs: num(e.execution_time_ms),
|
|
1166
|
+
iterations: num(e.iterations),
|
|
1167
|
+
startedAt: str(e.started_at),
|
|
1168
|
+
finishedAt: str(e.finished_at),
|
|
1169
|
+
raw
|
|
1170
|
+
};
|
|
1171
|
+
}
|
|
1172
|
+
async function delegateTask(cfg, params) {
|
|
1173
|
+
if (!params.agentId) throw new ZihinAgentError("config", "agentId is required.");
|
|
1174
|
+
if (!params.instruction) throw new ZihinAgentError("config", "instruction is required.");
|
|
1175
|
+
const scheduledFor = toIso(params.scheduledFor, "scheduledFor");
|
|
1176
|
+
const deadlineAt = toIso(params.deadlineAt, "deadlineAt");
|
|
1177
|
+
if (scheduledFor && Date.parse(scheduledFor) < Date.now() - SCHEDULE_PAST_TOLERANCE_MS) {
|
|
1178
|
+
throw new ZihinAgentError("config", "scheduledFor is in the past.");
|
|
1179
|
+
}
|
|
1180
|
+
if (deadlineAt && Date.parse(deadlineAt) <= Date.now() - SCHEDULE_PAST_TOLERANCE_MS) {
|
|
1181
|
+
throw new ZihinAgentError("config", "deadlineAt must be in the future.");
|
|
1182
|
+
}
|
|
1183
|
+
if (scheduledFor && deadlineAt && Date.parse(deadlineAt) <= Date.parse(scheduledFor)) {
|
|
1184
|
+
throw new ZihinAgentError("config", "deadlineAt must be after scheduledFor.");
|
|
1185
|
+
}
|
|
1186
|
+
if (params.payload !== void 0) {
|
|
1187
|
+
if (typeof params.payload !== "object" || params.payload === null || Array.isArray(params.payload)) {
|
|
1188
|
+
throw new ZihinAgentError("config", "payload must be an object.");
|
|
1189
|
+
}
|
|
1190
|
+
const bytes = toJsonBody(params.payload, "payload").length;
|
|
1191
|
+
if (bytes > MAX_TASK_PAYLOAD_BYTES) {
|
|
1192
|
+
throw new ZihinAgentError(
|
|
1193
|
+
"config",
|
|
1194
|
+
`payload exceeds ${MAX_TASK_PAYLOAD_BYTES / 1024}KB (got ${Math.ceil(bytes / 1024)}KB).`
|
|
1195
|
+
);
|
|
1196
|
+
}
|
|
1197
|
+
}
|
|
1198
|
+
const body = {
|
|
1199
|
+
agent_id: params.agentId,
|
|
1200
|
+
instruction: params.instruction
|
|
1201
|
+
};
|
|
1202
|
+
if (params.title !== void 0) body.title = params.title;
|
|
1203
|
+
if (params.payload !== void 0) body.payload = params.payload;
|
|
1204
|
+
if (scheduledFor) body.scheduled_for = scheduledFor;
|
|
1205
|
+
if (deadlineAt) body.deadline_at = deadlineAt;
|
|
1206
|
+
if (params.retryPolicy !== void 0) body.retry_policy = params.retryPolicy;
|
|
1207
|
+
if (params.notify !== void 0) body.notify = params.notify;
|
|
1208
|
+
if (params.parentTaskId) body.parent_task_id = params.parentTaskId;
|
|
1209
|
+
const json = await requestJson(cfg, {
|
|
1210
|
+
method: "POST",
|
|
1211
|
+
path: "/api/v1/tasks",
|
|
1212
|
+
body,
|
|
1213
|
+
// Criar uma task E dispará-la é efeito colateral: nunca repetir em erro de rede.
|
|
1214
|
+
policy: "side-effecting",
|
|
1215
|
+
signal: params.signal
|
|
923
1216
|
});
|
|
924
|
-
|
|
925
|
-
|
|
1217
|
+
const { data } = envelope(json);
|
|
1218
|
+
const task = mapTask(data.task ?? data);
|
|
1219
|
+
if (!task.id) {
|
|
1220
|
+
throw new ZihinAgentError("protocol", "delegateTask: response has no task id.");
|
|
926
1221
|
}
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
1222
|
+
return task;
|
|
1223
|
+
}
|
|
1224
|
+
async function listTasks(cfg, params = {}) {
|
|
1225
|
+
const qs = new URLSearchParams();
|
|
1226
|
+
if (params.live) qs.set("live", "true");
|
|
1227
|
+
if (params.status) qs.set("status", params.status);
|
|
1228
|
+
if (params.agentId) qs.set("agent_id", params.agentId);
|
|
1229
|
+
if (params.rootTaskId) qs.set("root_task_id", params.rootTaskId);
|
|
1230
|
+
if (params.limit !== void 0) qs.set("limit", String(params.limit));
|
|
1231
|
+
if (params.offset !== void 0) qs.set("offset", String(params.offset));
|
|
1232
|
+
const query = qs.toString();
|
|
1233
|
+
const json = await requestJson(cfg, {
|
|
1234
|
+
method: "GET",
|
|
1235
|
+
path: `/api/v1/tasks${query ? `?${query}` : ""}`,
|
|
1236
|
+
policy: "idempotent",
|
|
1237
|
+
signal: params.signal
|
|
1238
|
+
});
|
|
1239
|
+
const { data, meta } = envelope(json);
|
|
1240
|
+
const items = Array.isArray(data.items) ? data.items : [];
|
|
1241
|
+
return {
|
|
1242
|
+
items: items.map(mapTask),
|
|
1243
|
+
// ⚠️ `total` vive em `meta`, não no topo — o envelope novo aninhou os campos de paginação.
|
|
1244
|
+
total: num(meta.total),
|
|
1245
|
+
raw: json
|
|
1246
|
+
};
|
|
1247
|
+
}
|
|
1248
|
+
async function getTask(cfg, params) {
|
|
1249
|
+
if (!params.taskId) throw new ZihinAgentError("config", "taskId is required.");
|
|
1250
|
+
const json = await requestJson(cfg, {
|
|
1251
|
+
method: "GET",
|
|
1252
|
+
path: `/api/v1/tasks/${encodeURIComponent(params.taskId)}`,
|
|
1253
|
+
policy: "idempotent",
|
|
1254
|
+
signal: params.signal
|
|
1255
|
+
});
|
|
1256
|
+
const { data } = envelope(json);
|
|
1257
|
+
const taskRaw = data.task ?? (str(rec(data).id) ? data : void 0);
|
|
1258
|
+
if (!taskRaw || !str(rec(taskRaw).id)) {
|
|
1259
|
+
throw new ZihinAgentError("protocol", "getTask: response has no task.");
|
|
931
1260
|
}
|
|
1261
|
+
const executions = Array.isArray(data.executions) ? data.executions : [];
|
|
1262
|
+
const children = Array.isArray(data.children) ? data.children : [];
|
|
1263
|
+
return {
|
|
1264
|
+
task: mapTask(taskRaw),
|
|
1265
|
+
executions: executions.map(mapExecution),
|
|
1266
|
+
children: children.map(mapTask),
|
|
1267
|
+
approvals: Array.isArray(data.approvals) ? data.approvals : [],
|
|
1268
|
+
raw: json
|
|
1269
|
+
};
|
|
932
1270
|
}
|
|
1271
|
+
async function cancelTask(cfg, params) {
|
|
1272
|
+
if (!params.taskId) throw new ZihinAgentError("config", "taskId is required.");
|
|
1273
|
+
const json = await requestJson(cfg, {
|
|
1274
|
+
method: "POST",
|
|
1275
|
+
path: `/api/v1/tasks/${encodeURIComponent(params.taskId)}/cancel`,
|
|
1276
|
+
body: params.reason ? { reason: params.reason } : {},
|
|
1277
|
+
// Cancelar é side-effecting; repetir num socket caído bateria em 409 e confundiria o chamador.
|
|
1278
|
+
policy: "side-effecting",
|
|
1279
|
+
signal: params.signal
|
|
1280
|
+
});
|
|
1281
|
+
const { data } = envelope(json);
|
|
1282
|
+
const taskRaw = data.task ?? (str(rec(data).id) ? data : void 0);
|
|
1283
|
+
if (!taskRaw || !str(rec(taskRaw).id)) {
|
|
1284
|
+
throw new ZihinAgentError("protocol", "cancelTask: response has no task.");
|
|
1285
|
+
}
|
|
1286
|
+
return {
|
|
1287
|
+
task: mapTask(taskRaw),
|
|
1288
|
+
aborted: data.aborted === true,
|
|
1289
|
+
raw: json
|
|
1290
|
+
};
|
|
1291
|
+
}
|
|
1292
|
+
|
|
1293
|
+
// src/triggers.ts
|
|
933
1294
|
function queryExtractionBody(extraction, queryField) {
|
|
934
1295
|
if (!extraction) return { mode: "field", field: queryField ?? "message" };
|
|
935
1296
|
switch (extraction.mode) {
|
|
@@ -962,11 +1323,18 @@ async function createTrigger(cfg, params) {
|
|
|
962
1323
|
throw new ZihinAgentError("config", `Invalid callbackUrl: ${cb.error}`);
|
|
963
1324
|
}
|
|
964
1325
|
}
|
|
1326
|
+
if (params.responseTransform && !params.callbackUrl) {
|
|
1327
|
+
throw new ZihinAgentError(
|
|
1328
|
+
"config",
|
|
1329
|
+
"responseTransform requires callbackUrl: the backend only reads it from trigger_config.execution.callback.response_transform, and a callback without a url is rejected."
|
|
1330
|
+
);
|
|
1331
|
+
}
|
|
965
1332
|
const execution = { mode };
|
|
966
1333
|
if (params.callbackUrl) {
|
|
967
1334
|
execution.callback = {
|
|
968
1335
|
url: params.callbackUrl,
|
|
969
|
-
method: params.callbackMethod ?? "POST"
|
|
1336
|
+
method: params.callbackMethod ?? "POST",
|
|
1337
|
+
...params.responseTransform ? { response_transform: { mode: params.responseTransform } } : {}
|
|
970
1338
|
};
|
|
971
1339
|
}
|
|
972
1340
|
const body = {
|
|
@@ -996,8 +1364,8 @@ async function dispatchAgent(cfg, params) {
|
|
|
996
1364
|
method: "POST",
|
|
997
1365
|
url: `${cfg.baseUrl}/api/triggers/webhook/${encodeURIComponent(params.triggerId)}`,
|
|
998
1366
|
headers: buildHeaders(cfg.apiKey),
|
|
999
|
-
body:
|
|
1000
|
-
timeoutMs: cfg
|
|
1367
|
+
body: toJsonBody(params.payload ?? {}, "payload"),
|
|
1368
|
+
timeoutMs: resolveTimeout(cfg, "dispatch"),
|
|
1001
1369
|
maxRetries: cfg.maxRetries,
|
|
1002
1370
|
policy: "side-effecting",
|
|
1003
1371
|
// S1: nunca duplica disparo por erro de rede
|
|
@@ -1010,12 +1378,29 @@ async function dispatchAgent(cfg, params) {
|
|
|
1010
1378
|
}
|
|
1011
1379
|
const json = parseBody(res.text);
|
|
1012
1380
|
const j = rec(json);
|
|
1381
|
+
if (j.success === false) {
|
|
1382
|
+
const detail = sanitizeApiError(str(j.error) ?? "").message;
|
|
1383
|
+
throw new ZihinAgentError("server", detail || "The agent run failed.", {
|
|
1384
|
+
status: res.status,
|
|
1385
|
+
endpoint: res.endpoint,
|
|
1386
|
+
attempts: res.attempts,
|
|
1387
|
+
executionId: str(j.execution_id),
|
|
1388
|
+
// Não é transitório: o turno rodou e falhou. Repetir gastaria outra execução.
|
|
1389
|
+
retryable: false
|
|
1390
|
+
});
|
|
1391
|
+
}
|
|
1013
1392
|
return {
|
|
1014
1393
|
accepted: true,
|
|
1015
1394
|
triggerId: str(j.trigger_id) ?? params.triggerId,
|
|
1395
|
+
agentId: str(j.agent_id),
|
|
1016
1396
|
executionId: str(j.execution_id),
|
|
1017
1397
|
agentResponse: str(j.agent_response),
|
|
1018
1398
|
usage: parseUsage(j.usage),
|
|
1399
|
+
iterations: num(j.iterations),
|
|
1400
|
+
latencyMs: num(j.latency_ms),
|
|
1401
|
+
// Qual texto o BE extraiu do payload — é o campo que denuncia um `query_extraction` mal
|
|
1402
|
+
// configurado (mensagem vazia, ou o payload inteiro serializado virando a pergunta).
|
|
1403
|
+
queryExtracted: str(j.query_extracted),
|
|
1019
1404
|
raw: json
|
|
1020
1405
|
};
|
|
1021
1406
|
}
|
|
@@ -1071,7 +1456,7 @@ async function updateTrigger(cfg, params) {
|
|
|
1071
1456
|
if (params.name !== void 0) body.name = params.name;
|
|
1072
1457
|
if (params.description !== void 0) body.description = params.description;
|
|
1073
1458
|
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;
|
|
1459
|
+
const touchesConfig = params.queryExtraction !== void 0 || params.callbackUrl !== void 0 || params.callbackMethod !== void 0 || params.responseTransform !== void 0 || params.mode !== void 0;
|
|
1075
1460
|
if (touchesConfig) {
|
|
1076
1461
|
const path = `/api/triggers/${encodeURIComponent(params.triggerId)}`;
|
|
1077
1462
|
const currentJson = rec(
|
|
@@ -1089,22 +1474,34 @@ async function updateTrigger(cfg, params) {
|
|
|
1089
1474
|
if (params.queryExtraction !== void 0) {
|
|
1090
1475
|
merged.query_extraction = queryExtractionBody(params.queryExtraction, void 0);
|
|
1091
1476
|
}
|
|
1092
|
-
|
|
1477
|
+
const touchesCallback = params.callbackUrl !== void 0 || params.callbackMethod !== void 0 || params.responseTransform !== void 0;
|
|
1478
|
+
if (params.mode !== void 0 || touchesCallback) {
|
|
1093
1479
|
const execution = rec(config.execution);
|
|
1094
1480
|
const callback = rec(execution.callback);
|
|
1095
1481
|
const newExecution = { ...execution };
|
|
1096
1482
|
if (params.mode !== void 0) newExecution.mode = params.mode;
|
|
1097
|
-
if (
|
|
1483
|
+
if (touchesCallback) {
|
|
1098
1484
|
const url = params.callbackUrl ?? str(callback.url);
|
|
1099
1485
|
if (!url) {
|
|
1100
1486
|
throw new ZihinAgentError(
|
|
1101
1487
|
"config",
|
|
1102
|
-
"callbackUrl is required: this trigger has no callback configured yet."
|
|
1488
|
+
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
1489
|
);
|
|
1104
1490
|
}
|
|
1105
1491
|
newExecution.callback = {
|
|
1492
|
+
// Preserva o callback atual: trocar só a URL não pode apagar auth/headers/
|
|
1493
|
+
// body_template/content_type/response_transform. Antes este objeto era
|
|
1494
|
+
// SUBSTITUÍDO por { url, method } — a mesma perda silenciosa que o merge de
|
|
1495
|
+
// trigger_config acima existe para evitar, um nível mais fundo.
|
|
1496
|
+
...callback,
|
|
1106
1497
|
url,
|
|
1107
|
-
method: params.callbackMethod ?? str(callback.method) ?? "POST"
|
|
1498
|
+
method: params.callbackMethod ?? str(callback.method) ?? "POST",
|
|
1499
|
+
...params.responseTransform ? {
|
|
1500
|
+
response_transform: {
|
|
1501
|
+
...rec(callback.response_transform),
|
|
1502
|
+
mode: params.responseTransform
|
|
1503
|
+
}
|
|
1504
|
+
} : {}
|
|
1108
1505
|
};
|
|
1109
1506
|
}
|
|
1110
1507
|
if (newExecution.mode === "async" && !rec(newExecution.callback).url) {
|
|
@@ -1133,7 +1530,7 @@ async function deleteTrigger(cfg, params) {
|
|
|
1133
1530
|
method: "DELETE",
|
|
1134
1531
|
url: `${cfg.baseUrl}/api/triggers/${encodeURIComponent(params.triggerId)}`,
|
|
1135
1532
|
headers: buildHeaders(cfg.apiKey),
|
|
1136
|
-
timeoutMs: cfg
|
|
1533
|
+
timeoutMs: resolveTimeout(cfg, "short"),
|
|
1137
1534
|
maxRetries: cfg.maxRetries,
|
|
1138
1535
|
policy: "idempotent",
|
|
1139
1536
|
// DELETE é idempotente
|
|
@@ -1170,9 +1567,11 @@ async function getExecution(cfg, params) {
|
|
|
1170
1567
|
const data = rec(json.data);
|
|
1171
1568
|
const exec = rec(data.execution ?? json.execution);
|
|
1172
1569
|
const metrics = rec(data.metrics ?? json.metrics);
|
|
1570
|
+
const rawStatus = str(exec.status);
|
|
1173
1571
|
return {
|
|
1174
1572
|
executionId: str(exec.id) ?? params.executionId,
|
|
1175
|
-
status:
|
|
1573
|
+
status: rawStatus ?? "unknown",
|
|
1574
|
+
terminal: rawStatus === void 0 ? false : isTerminalStatus(rawStatus),
|
|
1176
1575
|
startedAt: str(exec.started_at),
|
|
1177
1576
|
finishedAt: str(exec.finished_at),
|
|
1178
1577
|
executionTimeMs: num(exec.execution_time_ms),
|
|
@@ -1253,6 +1652,26 @@ var ZihinAgentClient = class {
|
|
|
1253
1652
|
getExecution(params) {
|
|
1254
1653
|
return this.guard(getExecution(this.cfg, params));
|
|
1255
1654
|
}
|
|
1655
|
+
// ---- Caminho C: tasks (delegação assíncrona durável) ----
|
|
1656
|
+
/**
|
|
1657
|
+
* Delega uma tarefa a um agente (requer key owner/admin). Sem `scheduledFor`, o BE dispara na
|
|
1658
|
+
* hora fora do request e devolve só o handle — acompanhe com `getTask`.
|
|
1659
|
+
*/
|
|
1660
|
+
delegateTask(params) {
|
|
1661
|
+
return this.guard(delegateTask(this.cfg, params));
|
|
1662
|
+
}
|
|
1663
|
+
/** Board de tasks do tenant (requer key owner/admin/editor). */
|
|
1664
|
+
listTasks(params = {}) {
|
|
1665
|
+
return this.guard(listTasks(this.cfg, params));
|
|
1666
|
+
}
|
|
1667
|
+
/** Dossiê de uma task: task + execuções + filhos + aprovações. */
|
|
1668
|
+
getTask(params) {
|
|
1669
|
+
return this.guard(getTask(this.cfg, params));
|
|
1670
|
+
}
|
|
1671
|
+
/** Cancela uma task (requer key owner/admin); derruba o turno em voo, se houver. */
|
|
1672
|
+
cancelTask(params) {
|
|
1673
|
+
return this.guard(cancelTask(this.cfg, params));
|
|
1674
|
+
}
|
|
1256
1675
|
// ---- Auxiliar ----
|
|
1257
1676
|
/** Lista os agentes do tenant (dropdown). */
|
|
1258
1677
|
listAgents(opts = {}) {
|
|
@@ -1292,10 +1711,16 @@ function signRequest(payload, secretKey, timestamp) {
|
|
|
1292
1711
|
}
|
|
1293
1712
|
// Annotate the CommonJS export names for ESM import in node:
|
|
1294
1713
|
0 && (module.exports = {
|
|
1714
|
+
DEADLINE_GRACE_MS,
|
|
1295
1715
|
DEFAULT_LIMITS,
|
|
1716
|
+
MAX_TASK_PAYLOAD_BYTES,
|
|
1717
|
+
OPERATION_TIMEOUT_MS,
|
|
1296
1718
|
ZihinAgentClient,
|
|
1297
1719
|
ZihinAgentError,
|
|
1720
|
+
classifyStreamErrorKind,
|
|
1298
1721
|
createClient,
|
|
1722
|
+
errorFromStreamChunk,
|
|
1723
|
+
isCommercialBlock,
|
|
1299
1724
|
sanitizeApiError,
|
|
1300
1725
|
signRequest,
|
|
1301
1726
|
validateApiKeyFormat,
|