@zihin/agent-client 0.4.1 → 0.6.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/LICENSE +21 -0
- package/README.md +92 -1
- package/dist/index.d.mts +153 -9
- package/dist/index.d.ts +153 -9
- package/dist/index.js +300 -21
- package/dist/index.mjs +299 -21
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -13,6 +13,12 @@ function utf8Len(s) {
|
|
|
13
13
|
function rec(x) {
|
|
14
14
|
return x && typeof x === "object" ? x : {};
|
|
15
15
|
}
|
|
16
|
+
function safeId(x) {
|
|
17
|
+
return typeof x === "string" && /^[\w.:-]{1,128}$/.test(x) ? x : void 0;
|
|
18
|
+
}
|
|
19
|
+
function safeLabel(x) {
|
|
20
|
+
return typeof x === "string" && /^[A-Za-z0-9_]{1,64}$/.test(x) ? x : void 0;
|
|
21
|
+
}
|
|
16
22
|
function str(x) {
|
|
17
23
|
return typeof x === "string" ? x : void 0;
|
|
18
24
|
}
|
|
@@ -38,8 +44,13 @@ var RUNNING_STATUSES = /* @__PURE__ */ new Set([
|
|
|
38
44
|
"waiting",
|
|
39
45
|
"retrying",
|
|
40
46
|
"scheduled",
|
|
41
|
-
"awaiting_approval"
|
|
47
|
+
"awaiting_approval",
|
|
42
48
|
// HITL: turno pausado esperando decisão humana não terminou
|
|
49
|
+
// Estados VIVOS reais do backend (zihin-agent-builder `TaskService.LIVE_STATUSES` e
|
|
50
|
+
// `agent_executions.status`): sem eles, uma task/execução pausada ou aguardando input
|
|
51
|
+
// era lida como TERMINAL e o polling parava com o trabalho ainda em andamento.
|
|
52
|
+
"paused",
|
|
53
|
+
"awaiting_input"
|
|
43
54
|
]);
|
|
44
55
|
function isTerminalStatus(status) {
|
|
45
56
|
return !RUNNING_STATUSES.has(status.toLowerCase().replace(/-/g, "_"));
|
|
@@ -234,6 +245,9 @@ var ERROR_CODE_MAP = {
|
|
|
234
245
|
INSUFFICIENT_CREDITS: "Insufficient credits. Please check your account balance.",
|
|
235
246
|
CONTEXT_LENGTH_EXCEEDED: "The input exceeds the model context length limit.",
|
|
236
247
|
AGENT_NOT_FOUND: "Agent not found or not active for this tenant.",
|
|
248
|
+
// Nome novo do `AGENT_NOT_FOUND` (zihin-agent-builder#561, fatia B). `AGENT_ERROR` virou
|
|
249
|
+
// `INTERNAL_ERROR`, que já está acima.
|
|
250
|
+
NOT_FOUND: "Resource not found \u2014 check the ID (agent, trigger or execution) and that it belongs to this workspace.",
|
|
237
251
|
// Bloqueios comerciais. As mensagens do BE vêm em português e estes shells são em inglês;
|
|
238
252
|
// além disso cada caso tem uma ação DIFERENTE, e o texto precisa dizer qual.
|
|
239
253
|
TRIAL_EXPIRED: "Your Zihin trial has ended. Upgrade the plan to keep invoking agents \u2014 the API key is fine.",
|
|
@@ -304,6 +318,8 @@ var ZihinAgentError = class _ZihinAgentError extends Error {
|
|
|
304
318
|
this.code = opts.code;
|
|
305
319
|
this.requestId = opts.requestId;
|
|
306
320
|
this.executionId = opts.executionId;
|
|
321
|
+
this.sessionId = opts.sessionId;
|
|
322
|
+
this.timeoutClock = opts.timeoutClock;
|
|
307
323
|
this.attempts = opts.attempts;
|
|
308
324
|
this.endpoint = opts.endpoint;
|
|
309
325
|
this.retryable = opts.retryable ?? (kind === "rate_limit" || kind === "server" || kind === "network");
|
|
@@ -338,7 +354,7 @@ function commercialKind(body) {
|
|
|
338
354
|
}
|
|
339
355
|
function extractRequestId(body) {
|
|
340
356
|
const b = rec(body);
|
|
341
|
-
return
|
|
357
|
+
return safeId(b.request_id) ?? safeId(b.requestId) ?? safeId(rec(b.error).request_id) ?? safeId(rec(b.meta).request_id);
|
|
342
358
|
}
|
|
343
359
|
function errorFromResponse(status, body, meta = {}) {
|
|
344
360
|
const sanitized = sanitizeApiError(body);
|
|
@@ -348,7 +364,7 @@ function errorFromResponse(status, body, meta = {}) {
|
|
|
348
364
|
status,
|
|
349
365
|
code,
|
|
350
366
|
requestId: extractRequestId(body),
|
|
351
|
-
executionId:
|
|
367
|
+
executionId: safeId(rec(body).execution_id),
|
|
352
368
|
...meta
|
|
353
369
|
};
|
|
354
370
|
const commercial = commercialKind(body);
|
|
@@ -359,6 +375,12 @@ function errorFromResponse(status, body, meta = {}) {
|
|
|
359
375
|
});
|
|
360
376
|
}
|
|
361
377
|
if (status === 401) return new ZihinAgentError("auth", message || "Authentication failed.", base);
|
|
378
|
+
if (status === 403 && str(rec(rec(rec(body).error).details).reason) === "AGENT_ACCESS_DENIED")
|
|
379
|
+
return new ZihinAgentError(
|
|
380
|
+
"not_found",
|
|
381
|
+
"Agent not found, inactive, or not accessible with this API key \u2014 check the agent ID and that the key belongs to the same Zihin workspace.",
|
|
382
|
+
{ ...base, retryable: false }
|
|
383
|
+
);
|
|
362
384
|
if (status === 403)
|
|
363
385
|
return new ZihinAgentError(
|
|
364
386
|
"auth",
|
|
@@ -408,7 +430,7 @@ var OPERATION_TIMEOUT_MS = Object.freeze({
|
|
|
408
430
|
dispatch: 26e4,
|
|
409
431
|
short: 35e3
|
|
410
432
|
});
|
|
411
|
-
var DEADLINE_GRACE_MS =
|
|
433
|
+
var DEADLINE_GRACE_MS = 3e4;
|
|
412
434
|
var RETRY_OVERHEAD_MS = 6e4;
|
|
413
435
|
var MIN_TIMEOUT_MS = 1e3;
|
|
414
436
|
var MAX_TIMEOUT_MS = 9e5;
|
|
@@ -854,19 +876,34 @@ async function* parseSse(response, opts = {}) {
|
|
|
854
876
|
}
|
|
855
877
|
|
|
856
878
|
// src/invoke.ts
|
|
879
|
+
function parseAcceptedFields(v) {
|
|
880
|
+
if (!Array.isArray(v)) return void 0;
|
|
881
|
+
return v.map((x) => safeId(typeof x === "string" ? x : str(rec(x).field) ?? str(rec(x).name))).filter((x) => !!x);
|
|
882
|
+
}
|
|
883
|
+
function parseIgnoredFields(v) {
|
|
884
|
+
if (!Array.isArray(v)) return void 0;
|
|
885
|
+
const out = [];
|
|
886
|
+
for (const x of v) {
|
|
887
|
+
const field = safeId(typeof x === "string" ? x : str(rec(x).field) ?? str(rec(x).name));
|
|
888
|
+
if (field) out.push(typeof x === "string" ? { field } : { field, reason: safeLabel(rec(x).reason) });
|
|
889
|
+
}
|
|
890
|
+
return out;
|
|
891
|
+
}
|
|
857
892
|
function mapEvent(e) {
|
|
858
893
|
const d = rec(e.data);
|
|
859
894
|
switch (e.event) {
|
|
860
895
|
case "metadata":
|
|
861
896
|
return {
|
|
862
897
|
type: "metadata",
|
|
863
|
-
requestId:
|
|
898
|
+
requestId: safeId(d.request_id),
|
|
864
899
|
agentId: str(d.agent_id),
|
|
865
|
-
sessionId:
|
|
900
|
+
sessionId: safeId(d.session_id),
|
|
901
|
+
acceptedFields: parseAcceptedFields(d.accepted_fields),
|
|
902
|
+
ignoredFields: parseIgnoredFields(d.ignored_fields),
|
|
866
903
|
data: e.data
|
|
867
904
|
};
|
|
868
905
|
case "status":
|
|
869
|
-
return { type: "status", phase: str(d.phase), data: e.data };
|
|
906
|
+
return { type: "status", phase: str(d.phase), blocked: safeLabel(d.blocked), data: e.data };
|
|
870
907
|
case "attachments":
|
|
871
908
|
return { type: "attachments", data: e.data };
|
|
872
909
|
case "phase":
|
|
@@ -888,16 +925,24 @@ function mapEvent(e) {
|
|
|
888
925
|
case "warning":
|
|
889
926
|
return { type: "warning", message: str(d.message), data: e.data };
|
|
890
927
|
case "session":
|
|
891
|
-
return { type: "session", sessionId:
|
|
928
|
+
return { type: "session", sessionId: safeId(d.session_id), data: e.data };
|
|
892
929
|
case "done":
|
|
893
|
-
return {
|
|
930
|
+
return {
|
|
931
|
+
type: "done",
|
|
932
|
+
outcome: safeLabel(d.outcome),
|
|
933
|
+
timedOut: typeof d.timed_out === "boolean" ? d.timed_out : void 0,
|
|
934
|
+
timeoutClock: safeLabel(d.timeout_clock),
|
|
935
|
+
truncated: typeof d.truncated === "boolean" ? d.truncated : void 0,
|
|
936
|
+
finishReason: str(d.finish_reason),
|
|
937
|
+
data: e.data
|
|
938
|
+
};
|
|
894
939
|
case "error": {
|
|
895
940
|
const err = rec(d.error);
|
|
896
941
|
return {
|
|
897
942
|
type: "error",
|
|
898
|
-
code:
|
|
943
|
+
code: safeLabel(err.code),
|
|
899
944
|
message: str(err.message),
|
|
900
|
-
requestId:
|
|
945
|
+
requestId: safeId(err.request_id),
|
|
901
946
|
timedOut: typeof err.timed_out === "boolean" ? err.timed_out : void 0,
|
|
902
947
|
data: e.data
|
|
903
948
|
};
|
|
@@ -906,7 +951,8 @@ function mapEvent(e) {
|
|
|
906
951
|
return {
|
|
907
952
|
type: "response",
|
|
908
953
|
content: str(d.content) ?? "",
|
|
909
|
-
sessionId:
|
|
954
|
+
sessionId: safeId(d.session_id),
|
|
955
|
+
gated: d.gated === true ? true : void 0,
|
|
910
956
|
sources: Array.isArray(d.sources) ? d.sources : void 0,
|
|
911
957
|
blocks: Array.isArray(d.blocks) ? d.blocks : void 0,
|
|
912
958
|
data: e.data
|
|
@@ -919,6 +965,7 @@ function mapEvent(e) {
|
|
|
919
965
|
model: str(modelConfig.model) ?? str(d.model),
|
|
920
966
|
iterations: num(d.iterations),
|
|
921
967
|
executionTimeMs: num(d.execution_time_ms),
|
|
968
|
+
executionId: safeId(d.execution_id),
|
|
922
969
|
data: e.data
|
|
923
970
|
};
|
|
924
971
|
}
|
|
@@ -930,6 +977,10 @@ function buildRequestBody(params, normalizedTimeoutMs) {
|
|
|
930
977
|
const body = {};
|
|
931
978
|
if (params.message !== void 0) body.message = params.message;
|
|
932
979
|
if (params.sessionId) body.session_id = params.sessionId;
|
|
980
|
+
if (params.consumerKey) body.consumer_key = params.consumerKey;
|
|
981
|
+
if (params.context) body.context = { ...params.context };
|
|
982
|
+
if (params.bindings) body.bindings = { ...params.bindings };
|
|
983
|
+
if (params.sessionFrom?.length) body.session_from = [...params.sessionFrom];
|
|
933
984
|
if (params.attachments?.length) {
|
|
934
985
|
body.attachments = params.attachments.map((a) => ({ url: a.url, type: a.type }));
|
|
935
986
|
}
|
|
@@ -968,6 +1019,7 @@ function validateParams(params) {
|
|
|
968
1019
|
throw new ZihinAgentError("config", sizeCheck.error || "Message too large.");
|
|
969
1020
|
}
|
|
970
1021
|
}
|
|
1022
|
+
validateStructuredInput(params);
|
|
971
1023
|
if (params.options?.timeoutMs !== void 0) {
|
|
972
1024
|
normalizeTimeoutMs(params.options.timeoutMs, "options.timeoutMs");
|
|
973
1025
|
}
|
|
@@ -978,12 +1030,136 @@ function validateParams(params) {
|
|
|
978
1030
|
}
|
|
979
1031
|
}
|
|
980
1032
|
}
|
|
981
|
-
|
|
1033
|
+
var MAX_FIELDS = 50;
|
|
1034
|
+
var MAX_VALUE_CHARS = 2e3;
|
|
1035
|
+
var MAX_TOTAL_CHARS = { context: 2e3, bindings: 16e3 };
|
|
1036
|
+
var MAX_SESSION_FROM_FIELDS = 5;
|
|
1037
|
+
var FIELD_NAME_RE = /^[A-Za-z_][A-Za-z0-9_-]{0,63}$/;
|
|
1038
|
+
var PROTOTYPE_KEYS = /* @__PURE__ */ new Set(["__proto__", "constructor", "prototype"]);
|
|
1039
|
+
var MAX_CONSUMER_KEY_CHARS = 200;
|
|
1040
|
+
function snapshotStructuredInput(params) {
|
|
1041
|
+
const snap = (v) => isPlainObject(v) ? Object.fromEntries(Object.entries(v).filter(([, x]) => x !== void 0)) : v;
|
|
1042
|
+
return {
|
|
1043
|
+
...params,
|
|
1044
|
+
context: snap(params.context),
|
|
1045
|
+
bindings: snap(params.bindings),
|
|
1046
|
+
sessionFrom: Array.isArray(params.sessionFrom) ? [...params.sessionFrom] : params.sessionFrom
|
|
1047
|
+
};
|
|
1048
|
+
}
|
|
1049
|
+
function isPlainObject(v) {
|
|
1050
|
+
if (typeof v !== "object" || v === null || Array.isArray(v)) return false;
|
|
1051
|
+
const proto = Object.getPrototypeOf(v);
|
|
1052
|
+
return proto === Object.prototype || proto === null;
|
|
1053
|
+
}
|
|
1054
|
+
function isScalar(v) {
|
|
1055
|
+
return v === null || typeof v === "string" || typeof v === "boolean" || typeof v === "number" && Number.isFinite(v);
|
|
1056
|
+
}
|
|
1057
|
+
function validateStructuredInput(params) {
|
|
1058
|
+
if (params.consumerKey !== void 0 && (typeof params.consumerKey !== "string" || !params.consumerKey.trim())) {
|
|
1059
|
+
throw new ZihinAgentError("config", "`consumerKey` must be a non-empty string.");
|
|
1060
|
+
}
|
|
1061
|
+
if (typeof params.consumerKey === "string" && params.consumerKey.length > MAX_CONSUMER_KEY_CHARS) {
|
|
1062
|
+
throw new ZihinAgentError("config", `\`consumerKey\` is longer than ${MAX_CONSUMER_KEY_CHARS} characters.`);
|
|
1063
|
+
}
|
|
1064
|
+
for (const name of ["context", "bindings"]) {
|
|
1065
|
+
const v = params[name];
|
|
1066
|
+
if (v === void 0) continue;
|
|
1067
|
+
if (!isPlainObject(v)) {
|
|
1068
|
+
throw new ZihinAgentError("config", `\`${name}\` must be a flat plain object.`);
|
|
1069
|
+
}
|
|
1070
|
+
const entries = Object.entries(v);
|
|
1071
|
+
if (entries.length > MAX_FIELDS) {
|
|
1072
|
+
throw new ZihinAgentError("config", `\`${name}\` accepts at most ${MAX_FIELDS} fields.`);
|
|
1073
|
+
}
|
|
1074
|
+
for (const [k, val] of entries) {
|
|
1075
|
+
if (!FIELD_NAME_RE.test(k) || PROTOTYPE_KEYS.has(k)) {
|
|
1076
|
+
throw new ZihinAgentError(
|
|
1077
|
+
"config",
|
|
1078
|
+
`Invalid field name in \`${name}\`: use letters, digits, \`_\` or \`-\`, starting with a letter or \`_\` (max 64 characters; \`constructor\`, \`prototype\` and \`__proto__\` are not allowed).`
|
|
1079
|
+
);
|
|
1080
|
+
}
|
|
1081
|
+
if (typeof val === "string" && val.length > MAX_VALUE_CHARS) {
|
|
1082
|
+
throw new ZihinAgentError("config", `\`${name}.${k}\` is longer than ${MAX_VALUE_CHARS} characters.`);
|
|
1083
|
+
}
|
|
1084
|
+
if (!isScalar(val)) {
|
|
1085
|
+
throw new ZihinAgentError(
|
|
1086
|
+
"config",
|
|
1087
|
+
`\`${name}.${k}\` must be a string, number, boolean or null \u2014 \`${name}\` is flat (no nested objects or arrays).`
|
|
1088
|
+
);
|
|
1089
|
+
}
|
|
1090
|
+
}
|
|
1091
|
+
if (JSON.stringify(v).length > MAX_TOTAL_CHARS[name]) {
|
|
1092
|
+
throw new ZihinAgentError("config", `\`${name}\` is longer than ${MAX_TOTAL_CHARS[name]} characters in total (as JSON).`);
|
|
1093
|
+
}
|
|
1094
|
+
}
|
|
1095
|
+
if (params.sessionFrom !== void 0) {
|
|
1096
|
+
if (!Array.isArray(params.sessionFrom) || params.sessionFrom.length > MAX_SESSION_FROM_FIELDS || params.sessionFrom.some((f) => typeof f !== "string" || !FIELD_NAME_RE.test(f) || PROTOTYPE_KEYS.has(f))) {
|
|
1097
|
+
throw new ZihinAgentError("config", `\`sessionFrom\` must be an array of up to ${MAX_SESSION_FROM_FIELDS} field names.`);
|
|
1098
|
+
}
|
|
1099
|
+
if (params.sessionFrom.length && params.sessionId) {
|
|
1100
|
+
throw new ZihinAgentError("config", "Use either `sessionId` or `sessionFrom`, not both.");
|
|
1101
|
+
}
|
|
1102
|
+
const available = { ...params.context, ...params.bindings };
|
|
1103
|
+
const has = (f) => Object.prototype.hasOwnProperty.call(available, f) && Boolean(available[f]);
|
|
1104
|
+
if (params.sessionFrom.length && !params.sessionFrom.some(has)) {
|
|
1105
|
+
throw new ZihinAgentError(
|
|
1106
|
+
"config",
|
|
1107
|
+
`\`sessionFrom\` names ${params.sessionFrom.map((f) => `"${f}"`).join(", ")}, but none of them has a value in \`context\` or \`bindings\`.`
|
|
1108
|
+
);
|
|
1109
|
+
}
|
|
1110
|
+
}
|
|
1111
|
+
}
|
|
1112
|
+
function requestedInputFields(params) {
|
|
1113
|
+
const out = [];
|
|
1114
|
+
if (params.context) out.push("context");
|
|
1115
|
+
if (params.bindings) out.push("bindings");
|
|
1116
|
+
if (params.sessionFrom?.length) out.push("session_from");
|
|
1117
|
+
return out;
|
|
1118
|
+
}
|
|
1119
|
+
function checkInputCapability(params, chunk) {
|
|
1120
|
+
const requested = requestedInputFields(params);
|
|
1121
|
+
const ignored = chunk?.ignoredFields ?? [];
|
|
1122
|
+
const sent = [...requested, ...params.consumerKey ? ["consumer_key"] : []];
|
|
1123
|
+
const blocking = ignored.filter(
|
|
1124
|
+
(f) => f.reason !== "redacted" && sent.some((name) => f.field === name || f.field.startsWith(`${name}.`))
|
|
1125
|
+
);
|
|
1126
|
+
const tail = "the stream was closed to cancel the turn, but the message may already be recorded in the session.";
|
|
1127
|
+
if (blocking.length) {
|
|
1128
|
+
const shown = blocking.slice(0, 5);
|
|
1129
|
+
const more = blocking.length > shown.length ? ` (+${blocking.length - shown.length} more)` : "";
|
|
1130
|
+
const what = shown.map((f) => f.reason ? `\`${f.field}\` (${f.reason})` : `\`${f.field}\``).join(", ") + more;
|
|
1131
|
+
const hint = blocking.some((f) => f.reason === "reserved_key") ? " Reserved keys are names the agent engine uses internally; rename them." : blocking.some((f) => f.reason === "requires_admin_api_key") ? " These fields only apply with an admin/owner API key." : "";
|
|
1132
|
+
throw new ZihinAgentError("config", `The server ignored ${what}.${hint} The answer is not used: ${tail}`, {
|
|
1133
|
+
requestId: chunk?.requestId
|
|
1134
|
+
});
|
|
1135
|
+
}
|
|
1136
|
+
if (requested.length && chunk?.acceptedFields === void 0) {
|
|
1137
|
+
throw new ZihinAgentError(
|
|
1138
|
+
"config",
|
|
1139
|
+
`This Zihin server does not support ${requested.map((f) => `\`${f}\``).join(", ")} on /stream yet (no \`accepted_fields\` in the stream metadata) \u2014 ${tail}`,
|
|
1140
|
+
{ requestId: chunk?.requestId }
|
|
1141
|
+
);
|
|
1142
|
+
}
|
|
1143
|
+
const mustAccept = chunk?.acceptedFields !== void 0 && params.consumerKey ? [...requested, "consumer_key"] : requested;
|
|
1144
|
+
const notAccepted = mustAccept.filter((f) => !chunk?.acceptedFields?.includes(f));
|
|
1145
|
+
if (notAccepted.length) {
|
|
1146
|
+
throw new ZihinAgentError(
|
|
1147
|
+
"config",
|
|
1148
|
+
`The server did not accept ${notAccepted.map((f) => `\`${f}\``).join(", ")} (missing from \`accepted_fields\`) \u2014 ${tail}`,
|
|
1149
|
+
{ requestId: chunk?.requestId }
|
|
1150
|
+
);
|
|
1151
|
+
}
|
|
1152
|
+
}
|
|
1153
|
+
var TURN_EVENTS = /* @__PURE__ */ new Set(["phase", "thinking", "tool", "action_proposed", "token", "response", "metrics", "done"]);
|
|
1154
|
+
async function* streamAgent(cfg, rawParams) {
|
|
1155
|
+
const params = snapshotStructuredInput(rawParams);
|
|
982
1156
|
validateParams(params);
|
|
983
1157
|
const callerTimeoutMs = params.options?.timeoutMs !== void 0 ? normalizeTimeoutMs(params.options.timeoutMs, "options.timeoutMs") : void 0;
|
|
984
1158
|
const timeoutMs = callerTimeoutMs !== void 0 ? callerTimeoutMs + DEADLINE_GRACE_MS : resolveTimeout(cfg, "stream");
|
|
985
1159
|
const reportedMs = callerTimeoutMs ?? timeoutMs;
|
|
986
1160
|
const url = `${cfg.baseUrl}/api/v2/agents/${encodeURIComponent(params.agentId)}/stream`;
|
|
1161
|
+
let needsCapability = requestedInputFields(params).length > 0 || !!params.consumerKey;
|
|
1162
|
+
let gated = false;
|
|
987
1163
|
const { response, deadline, endpoint } = await openStream({
|
|
988
1164
|
url,
|
|
989
1165
|
headers: buildHeaders(cfg.apiKey),
|
|
@@ -1011,7 +1187,14 @@ async function* streamAgent(cfg, params) {
|
|
|
1011
1187
|
maxEventBytes: cfg.maxStreamEventBytes,
|
|
1012
1188
|
maxTotalBytes: cfg.maxStreamTotalBytes
|
|
1013
1189
|
})) {
|
|
1014
|
-
|
|
1190
|
+
const chunk = mapEvent(e);
|
|
1191
|
+
if (needsCapability) {
|
|
1192
|
+
if (chunk.type === "metadata") checkInputCapability(params, chunk);
|
|
1193
|
+
else if (chunk.type === "status" && chunk.phase === "GATED") gated = true;
|
|
1194
|
+
else if (TURN_EVENTS.has(chunk.type) && !gated) checkInputCapability(params, void 0);
|
|
1195
|
+
if (chunk.type === "metadata" || TURN_EVENTS.has(chunk.type) && !gated) needsCapability = false;
|
|
1196
|
+
}
|
|
1197
|
+
yield chunk;
|
|
1015
1198
|
}
|
|
1016
1199
|
} finally {
|
|
1017
1200
|
deadline.dispose();
|
|
@@ -1019,6 +1202,7 @@ async function* streamAgent(cfg, params) {
|
|
|
1019
1202
|
}
|
|
1020
1203
|
function classifyStreamErrorKind(code, timedOut) {
|
|
1021
1204
|
if (timedOut === true) return "timeout";
|
|
1205
|
+
if (code === "VALIDATION_ERROR") return "config";
|
|
1022
1206
|
if (timedOut === false) return "server";
|
|
1023
1207
|
return /TIMEOUT/i.test(code) ? "timeout" : "server";
|
|
1024
1208
|
}
|
|
@@ -1031,6 +1215,77 @@ function errorFromStreamChunk(chunk) {
|
|
|
1031
1215
|
retryable: false
|
|
1032
1216
|
});
|
|
1033
1217
|
}
|
|
1218
|
+
var FAILED_OUTCOMES = {
|
|
1219
|
+
ERROR: { kind: "server", message: "The agent turn failed on the server; its text is the engine failure notice, not an answer." },
|
|
1220
|
+
// `server`, não `aborted`: `aborted` é o cancelamento do PRÓPRIO chamador, que `onError` manda
|
|
1221
|
+
// filtrar — um cancelamento feito pelo operador sumiria junto.
|
|
1222
|
+
CANCELLED: { kind: "server", message: "The agent turn was cancelled on the server (by an operator or a platform guard)." },
|
|
1223
|
+
// Modo processo drenando/encerrando; o motor retoma depois. Nenhum canal atual o produz.
|
|
1224
|
+
SUSPENDED: { kind: "blocked", message: "The agent turn was suspended by the server and did not answer; retry later." },
|
|
1225
|
+
BUDGET_EXCEEDED: { kind: "blocked", message: "The agent's spending cap was reached; raise the agent budget to keep invoking it." },
|
|
1226
|
+
QUOTA_EXCEEDED: { kind: "quota_exceeded", message: "Your Zihin token quota is exhausted. Upgrade the plan or wait for the next cycle." },
|
|
1227
|
+
SESSION_BUSY: {
|
|
1228
|
+
kind: "blocked",
|
|
1229
|
+
message: "Another turn is still running in this session; retry after it finishes.",
|
|
1230
|
+
retryable: true
|
|
1231
|
+
},
|
|
1232
|
+
// Hoje a rota manda CONFLICT como `event: error` (não chega ao `done`); mapeado por defesa.
|
|
1233
|
+
CONFLICT: {
|
|
1234
|
+
kind: "blocked",
|
|
1235
|
+
message: "The declared consumer identity does not match the one already bound to this session."
|
|
1236
|
+
}
|
|
1237
|
+
};
|
|
1238
|
+
var PAUSED_BLOCKS = /* @__PURE__ */ new Set(["agent_paused", "agent_archived"]);
|
|
1239
|
+
var ERROR_BLOCKS = {
|
|
1240
|
+
ownership_denied: {
|
|
1241
|
+
kind: "not_found",
|
|
1242
|
+
message: "Session not found for this API key \u2014 start a new conversation (omit sessionId) instead of reusing this one."
|
|
1243
|
+
},
|
|
1244
|
+
session_load_failed: { kind: "blocked", message: "The server could not load this conversation right now; retry shortly.", retryable: true },
|
|
1245
|
+
approval_resolve_failed: { kind: "blocked", message: "The server could not route the required approval right now; retry shortly.", retryable: true },
|
|
1246
|
+
approval_no_approver: { kind: "blocked", message: "This action requires approval, but the approval policy has no approver configured." },
|
|
1247
|
+
blocked_schedule: { kind: "blocked", message: "The agent is outside its allowed schedule (security policy)." },
|
|
1248
|
+
owner_inactive: { kind: "blocked", message: "The agent's owner is inactive; the agent cannot be invoked." }
|
|
1249
|
+
};
|
|
1250
|
+
function own(table, key) {
|
|
1251
|
+
return key !== void 0 && Object.prototype.hasOwnProperty.call(table, key) ? table[key] : void 0;
|
|
1252
|
+
}
|
|
1253
|
+
function errorFromDoneChunk(chunk, ctx = {}) {
|
|
1254
|
+
const meta = { executionId: ctx.executionId, sessionId: ctx.sessionId };
|
|
1255
|
+
const blockedReason = safeLabel(ctx.blocked);
|
|
1256
|
+
if (chunk.outcome === "TIMEOUT" || chunk.timedOut === true) {
|
|
1257
|
+
const clock = chunk.timeoutClock;
|
|
1258
|
+
const what = clock === "turn" ? "the turn deadline" : clock === "first_signal" ? "the model provider did not start responding in time" : clock === "idle" ? "the model provider stream went silent" : clock === "call" ? "the model call took too long" : "a server-side clock";
|
|
1259
|
+
return new ZihinAgentError("timeout", `The agent turn timed out (${what}).`, {
|
|
1260
|
+
...meta,
|
|
1261
|
+
code: "TURN_TIMEOUT",
|
|
1262
|
+
retryable: false,
|
|
1263
|
+
timeoutClock: clock
|
|
1264
|
+
});
|
|
1265
|
+
}
|
|
1266
|
+
if (chunk.outcome === "PAUSED" && blockedReason && PAUSED_BLOCKS.has(blockedReason)) {
|
|
1267
|
+
return new ZihinAgentError("blocked", "The agent is paused or archived and did not answer.", {
|
|
1268
|
+
...meta,
|
|
1269
|
+
code: "PAUSED",
|
|
1270
|
+
retryable: false
|
|
1271
|
+
});
|
|
1272
|
+
}
|
|
1273
|
+
if (chunk.outcome === "ERROR" && blockedReason) {
|
|
1274
|
+
const gate = own(ERROR_BLOCKS, blockedReason);
|
|
1275
|
+
return new ZihinAgentError(gate?.kind ?? "blocked", gate?.message ?? `The agent turn was blocked by the server (${blockedReason}).`, {
|
|
1276
|
+
...meta,
|
|
1277
|
+
code: blockedReason.toUpperCase(),
|
|
1278
|
+
retryable: gate?.retryable ?? false
|
|
1279
|
+
});
|
|
1280
|
+
}
|
|
1281
|
+
const failed = own(FAILED_OUTCOMES, chunk.outcome);
|
|
1282
|
+
if (!failed) return void 0;
|
|
1283
|
+
return new ZihinAgentError(failed.kind, failed.message, {
|
|
1284
|
+
...meta,
|
|
1285
|
+
code: chunk.outcome,
|
|
1286
|
+
retryable: failed.retryable ?? false
|
|
1287
|
+
});
|
|
1288
|
+
}
|
|
1034
1289
|
async function invokeAgent(cfg, params) {
|
|
1035
1290
|
const raw = [];
|
|
1036
1291
|
const keepRaw = params.includeRaw === true;
|
|
@@ -1038,7 +1293,10 @@ async function invokeAgent(cfg, params) {
|
|
|
1038
1293
|
let metricsChunk;
|
|
1039
1294
|
let metadataSessionId;
|
|
1040
1295
|
let mintedSessionId;
|
|
1041
|
-
let
|
|
1296
|
+
let doneChunk;
|
|
1297
|
+
let blocked;
|
|
1298
|
+
let acceptedFields;
|
|
1299
|
+
let ignoredFields;
|
|
1042
1300
|
let tokenBuffer = "";
|
|
1043
1301
|
for await (const chunk of streamAgent(cfg, params)) {
|
|
1044
1302
|
if (keepRaw) raw.push(chunk);
|
|
@@ -1048,10 +1306,19 @@ async function invokeAgent(cfg, params) {
|
|
|
1048
1306
|
if (chunk.type === "response") responseChunk = chunk;
|
|
1049
1307
|
else if (chunk.type === "metrics") metricsChunk = chunk;
|
|
1050
1308
|
else if (chunk.type === "token") tokenBuffer += chunk.content;
|
|
1051
|
-
else if (chunk.type === "metadata"
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1309
|
+
else if (chunk.type === "metadata") {
|
|
1310
|
+
if (chunk.sessionId) metadataSessionId = chunk.sessionId;
|
|
1311
|
+
acceptedFields = chunk.acceptedFields;
|
|
1312
|
+
ignoredFields = chunk.ignoredFields;
|
|
1313
|
+
} else if (chunk.type === "session" && chunk.sessionId) mintedSessionId = chunk.sessionId;
|
|
1314
|
+
else if (chunk.type === "done") doneChunk = chunk;
|
|
1315
|
+
else if (chunk.type === "status" && chunk.blocked) blocked = chunk.blocked;
|
|
1316
|
+
}
|
|
1317
|
+
const sessionId = mintedSessionId ?? responseChunk?.sessionId ?? metadataSessionId ?? params.sessionId;
|
|
1318
|
+
const executionId = metricsChunk?.executionId;
|
|
1319
|
+
const outcomeError = doneChunk && errorFromDoneChunk(doneChunk, { executionId, sessionId, blocked });
|
|
1320
|
+
if (outcomeError) throw outcomeError;
|
|
1321
|
+
const cleanEnd = doneChunk !== void 0;
|
|
1055
1322
|
if (!responseChunk) {
|
|
1056
1323
|
if (cleanEnd) {
|
|
1057
1324
|
throw new ZihinAgentError("protocol", "Stream ended (done) without a `response` event.");
|
|
@@ -1060,11 +1327,14 @@ async function invokeAgent(cfg, params) {
|
|
|
1060
1327
|
return {
|
|
1061
1328
|
content: tokenBuffer,
|
|
1062
1329
|
contentSource: "tokens",
|
|
1063
|
-
sessionId
|
|
1330
|
+
sessionId,
|
|
1064
1331
|
usage: metricsChunk?.usage,
|
|
1065
1332
|
model: metricsChunk?.model,
|
|
1066
1333
|
iterations: metricsChunk?.iterations,
|
|
1067
1334
|
executionTimeMs: metricsChunk?.executionTimeMs,
|
|
1335
|
+
executionId,
|
|
1336
|
+
acceptedFields,
|
|
1337
|
+
ignoredFields,
|
|
1068
1338
|
raw
|
|
1069
1339
|
};
|
|
1070
1340
|
}
|
|
@@ -1073,13 +1343,20 @@ async function invokeAgent(cfg, params) {
|
|
|
1073
1343
|
return {
|
|
1074
1344
|
content: responseChunk.content,
|
|
1075
1345
|
contentSource: "response",
|
|
1076
|
-
sessionId
|
|
1346
|
+
sessionId,
|
|
1077
1347
|
model: metricsChunk?.model,
|
|
1078
1348
|
usage: metricsChunk?.usage,
|
|
1079
1349
|
sources: responseChunk.sources,
|
|
1080
1350
|
blocks: responseChunk.blocks,
|
|
1351
|
+
gated: responseChunk.gated,
|
|
1081
1352
|
iterations: metricsChunk?.iterations,
|
|
1082
1353
|
executionTimeMs: metricsChunk?.executionTimeMs,
|
|
1354
|
+
executionId,
|
|
1355
|
+
outcome: doneChunk?.outcome,
|
|
1356
|
+
truncated: doneChunk?.truncated,
|
|
1357
|
+
finishReason: doneChunk?.finishReason,
|
|
1358
|
+
acceptedFields,
|
|
1359
|
+
ignoredFields,
|
|
1083
1360
|
raw
|
|
1084
1361
|
};
|
|
1085
1362
|
}
|
|
@@ -1685,6 +1962,7 @@ export {
|
|
|
1685
1962
|
ZihinAgentError,
|
|
1686
1963
|
classifyStreamErrorKind,
|
|
1687
1964
|
createClient,
|
|
1965
|
+
errorFromDoneChunk,
|
|
1688
1966
|
errorFromStreamChunk,
|
|
1689
1967
|
isCommercialBlock,
|
|
1690
1968
|
sanitizeApiError,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zihin/agent-client",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
4
4
|
"description": "Núcleo de invocação de agentes Zihin.ai — padrão único para n8n, Paperclip e integrações futuras. Stream direto (SSE bufferizado) + triggers assíncronos com callback.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"zihin",
|