@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.js
CHANGED
|
@@ -28,6 +28,7 @@ __export(index_exports, {
|
|
|
28
28
|
ZihinAgentError: () => ZihinAgentError,
|
|
29
29
|
classifyStreamErrorKind: () => classifyStreamErrorKind,
|
|
30
30
|
createClient: () => createClient,
|
|
31
|
+
errorFromDoneChunk: () => errorFromDoneChunk,
|
|
31
32
|
errorFromStreamChunk: () => errorFromStreamChunk,
|
|
32
33
|
isCommercialBlock: () => isCommercialBlock,
|
|
33
34
|
sanitizeApiError: () => sanitizeApiError,
|
|
@@ -46,6 +47,12 @@ function utf8Len(s) {
|
|
|
46
47
|
function rec(x) {
|
|
47
48
|
return x && typeof x === "object" ? x : {};
|
|
48
49
|
}
|
|
50
|
+
function safeId(x) {
|
|
51
|
+
return typeof x === "string" && /^[\w.:-]{1,128}$/.test(x) ? x : void 0;
|
|
52
|
+
}
|
|
53
|
+
function safeLabel(x) {
|
|
54
|
+
return typeof x === "string" && /^[A-Za-z0-9_]{1,64}$/.test(x) ? x : void 0;
|
|
55
|
+
}
|
|
49
56
|
function str(x) {
|
|
50
57
|
return typeof x === "string" ? x : void 0;
|
|
51
58
|
}
|
|
@@ -71,8 +78,13 @@ var RUNNING_STATUSES = /* @__PURE__ */ new Set([
|
|
|
71
78
|
"waiting",
|
|
72
79
|
"retrying",
|
|
73
80
|
"scheduled",
|
|
74
|
-
"awaiting_approval"
|
|
81
|
+
"awaiting_approval",
|
|
75
82
|
// HITL: turno pausado esperando decisão humana não terminou
|
|
83
|
+
// Estados VIVOS reais do backend (zihin-agent-builder `TaskService.LIVE_STATUSES` e
|
|
84
|
+
// `agent_executions.status`): sem eles, uma task/execução pausada ou aguardando input
|
|
85
|
+
// era lida como TERMINAL e o polling parava com o trabalho ainda em andamento.
|
|
86
|
+
"paused",
|
|
87
|
+
"awaiting_input"
|
|
76
88
|
]);
|
|
77
89
|
function isTerminalStatus(status) {
|
|
78
90
|
return !RUNNING_STATUSES.has(status.toLowerCase().replace(/-/g, "_"));
|
|
@@ -267,6 +279,9 @@ var ERROR_CODE_MAP = {
|
|
|
267
279
|
INSUFFICIENT_CREDITS: "Insufficient credits. Please check your account balance.",
|
|
268
280
|
CONTEXT_LENGTH_EXCEEDED: "The input exceeds the model context length limit.",
|
|
269
281
|
AGENT_NOT_FOUND: "Agent not found or not active for this tenant.",
|
|
282
|
+
// Nome novo do `AGENT_NOT_FOUND` (zihin-agent-builder#561, fatia B). `AGENT_ERROR` virou
|
|
283
|
+
// `INTERNAL_ERROR`, que já está acima.
|
|
284
|
+
NOT_FOUND: "Resource not found \u2014 check the ID (agent, trigger or execution) and that it belongs to this workspace.",
|
|
270
285
|
// Bloqueios comerciais. As mensagens do BE vêm em português e estes shells são em inglês;
|
|
271
286
|
// além disso cada caso tem uma ação DIFERENTE, e o texto precisa dizer qual.
|
|
272
287
|
TRIAL_EXPIRED: "Your Zihin trial has ended. Upgrade the plan to keep invoking agents \u2014 the API key is fine.",
|
|
@@ -337,6 +352,8 @@ var ZihinAgentError = class _ZihinAgentError extends Error {
|
|
|
337
352
|
this.code = opts.code;
|
|
338
353
|
this.requestId = opts.requestId;
|
|
339
354
|
this.executionId = opts.executionId;
|
|
355
|
+
this.sessionId = opts.sessionId;
|
|
356
|
+
this.timeoutClock = opts.timeoutClock;
|
|
340
357
|
this.attempts = opts.attempts;
|
|
341
358
|
this.endpoint = opts.endpoint;
|
|
342
359
|
this.retryable = opts.retryable ?? (kind === "rate_limit" || kind === "server" || kind === "network");
|
|
@@ -371,7 +388,7 @@ function commercialKind(body) {
|
|
|
371
388
|
}
|
|
372
389
|
function extractRequestId(body) {
|
|
373
390
|
const b = rec(body);
|
|
374
|
-
return
|
|
391
|
+
return safeId(b.request_id) ?? safeId(b.requestId) ?? safeId(rec(b.error).request_id) ?? safeId(rec(b.meta).request_id);
|
|
375
392
|
}
|
|
376
393
|
function errorFromResponse(status, body, meta = {}) {
|
|
377
394
|
const sanitized = sanitizeApiError(body);
|
|
@@ -381,7 +398,7 @@ function errorFromResponse(status, body, meta = {}) {
|
|
|
381
398
|
status,
|
|
382
399
|
code,
|
|
383
400
|
requestId: extractRequestId(body),
|
|
384
|
-
executionId:
|
|
401
|
+
executionId: safeId(rec(body).execution_id),
|
|
385
402
|
...meta
|
|
386
403
|
};
|
|
387
404
|
const commercial = commercialKind(body);
|
|
@@ -392,6 +409,12 @@ function errorFromResponse(status, body, meta = {}) {
|
|
|
392
409
|
});
|
|
393
410
|
}
|
|
394
411
|
if (status === 401) return new ZihinAgentError("auth", message || "Authentication failed.", base);
|
|
412
|
+
if (status === 403 && str(rec(rec(rec(body).error).details).reason) === "AGENT_ACCESS_DENIED")
|
|
413
|
+
return new ZihinAgentError(
|
|
414
|
+
"not_found",
|
|
415
|
+
"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.",
|
|
416
|
+
{ ...base, retryable: false }
|
|
417
|
+
);
|
|
395
418
|
if (status === 403)
|
|
396
419
|
return new ZihinAgentError(
|
|
397
420
|
"auth",
|
|
@@ -441,7 +464,7 @@ var OPERATION_TIMEOUT_MS = Object.freeze({
|
|
|
441
464
|
dispatch: 26e4,
|
|
442
465
|
short: 35e3
|
|
443
466
|
});
|
|
444
|
-
var DEADLINE_GRACE_MS =
|
|
467
|
+
var DEADLINE_GRACE_MS = 3e4;
|
|
445
468
|
var RETRY_OVERHEAD_MS = 6e4;
|
|
446
469
|
var MIN_TIMEOUT_MS = 1e3;
|
|
447
470
|
var MAX_TIMEOUT_MS = 9e5;
|
|
@@ -887,19 +910,34 @@ async function* parseSse(response, opts = {}) {
|
|
|
887
910
|
}
|
|
888
911
|
|
|
889
912
|
// src/invoke.ts
|
|
913
|
+
function parseAcceptedFields(v) {
|
|
914
|
+
if (!Array.isArray(v)) return void 0;
|
|
915
|
+
return v.map((x) => safeId(typeof x === "string" ? x : str(rec(x).field) ?? str(rec(x).name))).filter((x) => !!x);
|
|
916
|
+
}
|
|
917
|
+
function parseIgnoredFields(v) {
|
|
918
|
+
if (!Array.isArray(v)) return void 0;
|
|
919
|
+
const out = [];
|
|
920
|
+
for (const x of v) {
|
|
921
|
+
const field = safeId(typeof x === "string" ? x : str(rec(x).field) ?? str(rec(x).name));
|
|
922
|
+
if (field) out.push(typeof x === "string" ? { field } : { field, reason: safeLabel(rec(x).reason) });
|
|
923
|
+
}
|
|
924
|
+
return out;
|
|
925
|
+
}
|
|
890
926
|
function mapEvent(e) {
|
|
891
927
|
const d = rec(e.data);
|
|
892
928
|
switch (e.event) {
|
|
893
929
|
case "metadata":
|
|
894
930
|
return {
|
|
895
931
|
type: "metadata",
|
|
896
|
-
requestId:
|
|
932
|
+
requestId: safeId(d.request_id),
|
|
897
933
|
agentId: str(d.agent_id),
|
|
898
|
-
sessionId:
|
|
934
|
+
sessionId: safeId(d.session_id),
|
|
935
|
+
acceptedFields: parseAcceptedFields(d.accepted_fields),
|
|
936
|
+
ignoredFields: parseIgnoredFields(d.ignored_fields),
|
|
899
937
|
data: e.data
|
|
900
938
|
};
|
|
901
939
|
case "status":
|
|
902
|
-
return { type: "status", phase: str(d.phase), data: e.data };
|
|
940
|
+
return { type: "status", phase: str(d.phase), blocked: safeLabel(d.blocked), data: e.data };
|
|
903
941
|
case "attachments":
|
|
904
942
|
return { type: "attachments", data: e.data };
|
|
905
943
|
case "phase":
|
|
@@ -921,16 +959,24 @@ function mapEvent(e) {
|
|
|
921
959
|
case "warning":
|
|
922
960
|
return { type: "warning", message: str(d.message), data: e.data };
|
|
923
961
|
case "session":
|
|
924
|
-
return { type: "session", sessionId:
|
|
962
|
+
return { type: "session", sessionId: safeId(d.session_id), data: e.data };
|
|
925
963
|
case "done":
|
|
926
|
-
return {
|
|
964
|
+
return {
|
|
965
|
+
type: "done",
|
|
966
|
+
outcome: safeLabel(d.outcome),
|
|
967
|
+
timedOut: typeof d.timed_out === "boolean" ? d.timed_out : void 0,
|
|
968
|
+
timeoutClock: safeLabel(d.timeout_clock),
|
|
969
|
+
truncated: typeof d.truncated === "boolean" ? d.truncated : void 0,
|
|
970
|
+
finishReason: str(d.finish_reason),
|
|
971
|
+
data: e.data
|
|
972
|
+
};
|
|
927
973
|
case "error": {
|
|
928
974
|
const err = rec(d.error);
|
|
929
975
|
return {
|
|
930
976
|
type: "error",
|
|
931
|
-
code:
|
|
977
|
+
code: safeLabel(err.code),
|
|
932
978
|
message: str(err.message),
|
|
933
|
-
requestId:
|
|
979
|
+
requestId: safeId(err.request_id),
|
|
934
980
|
timedOut: typeof err.timed_out === "boolean" ? err.timed_out : void 0,
|
|
935
981
|
data: e.data
|
|
936
982
|
};
|
|
@@ -939,7 +985,8 @@ function mapEvent(e) {
|
|
|
939
985
|
return {
|
|
940
986
|
type: "response",
|
|
941
987
|
content: str(d.content) ?? "",
|
|
942
|
-
sessionId:
|
|
988
|
+
sessionId: safeId(d.session_id),
|
|
989
|
+
gated: d.gated === true ? true : void 0,
|
|
943
990
|
sources: Array.isArray(d.sources) ? d.sources : void 0,
|
|
944
991
|
blocks: Array.isArray(d.blocks) ? d.blocks : void 0,
|
|
945
992
|
data: e.data
|
|
@@ -952,6 +999,7 @@ function mapEvent(e) {
|
|
|
952
999
|
model: str(modelConfig.model) ?? str(d.model),
|
|
953
1000
|
iterations: num(d.iterations),
|
|
954
1001
|
executionTimeMs: num(d.execution_time_ms),
|
|
1002
|
+
executionId: safeId(d.execution_id),
|
|
955
1003
|
data: e.data
|
|
956
1004
|
};
|
|
957
1005
|
}
|
|
@@ -963,6 +1011,10 @@ function buildRequestBody(params, normalizedTimeoutMs) {
|
|
|
963
1011
|
const body = {};
|
|
964
1012
|
if (params.message !== void 0) body.message = params.message;
|
|
965
1013
|
if (params.sessionId) body.session_id = params.sessionId;
|
|
1014
|
+
if (params.consumerKey) body.consumer_key = params.consumerKey;
|
|
1015
|
+
if (params.context) body.context = { ...params.context };
|
|
1016
|
+
if (params.bindings) body.bindings = { ...params.bindings };
|
|
1017
|
+
if (params.sessionFrom?.length) body.session_from = [...params.sessionFrom];
|
|
966
1018
|
if (params.attachments?.length) {
|
|
967
1019
|
body.attachments = params.attachments.map((a) => ({ url: a.url, type: a.type }));
|
|
968
1020
|
}
|
|
@@ -1001,6 +1053,7 @@ function validateParams(params) {
|
|
|
1001
1053
|
throw new ZihinAgentError("config", sizeCheck.error || "Message too large.");
|
|
1002
1054
|
}
|
|
1003
1055
|
}
|
|
1056
|
+
validateStructuredInput(params);
|
|
1004
1057
|
if (params.options?.timeoutMs !== void 0) {
|
|
1005
1058
|
normalizeTimeoutMs(params.options.timeoutMs, "options.timeoutMs");
|
|
1006
1059
|
}
|
|
@@ -1011,12 +1064,136 @@ function validateParams(params) {
|
|
|
1011
1064
|
}
|
|
1012
1065
|
}
|
|
1013
1066
|
}
|
|
1014
|
-
|
|
1067
|
+
var MAX_FIELDS = 50;
|
|
1068
|
+
var MAX_VALUE_CHARS = 2e3;
|
|
1069
|
+
var MAX_TOTAL_CHARS = { context: 2e3, bindings: 16e3 };
|
|
1070
|
+
var MAX_SESSION_FROM_FIELDS = 5;
|
|
1071
|
+
var FIELD_NAME_RE = /^[A-Za-z_][A-Za-z0-9_-]{0,63}$/;
|
|
1072
|
+
var PROTOTYPE_KEYS = /* @__PURE__ */ new Set(["__proto__", "constructor", "prototype"]);
|
|
1073
|
+
var MAX_CONSUMER_KEY_CHARS = 200;
|
|
1074
|
+
function snapshotStructuredInput(params) {
|
|
1075
|
+
const snap = (v) => isPlainObject(v) ? Object.fromEntries(Object.entries(v).filter(([, x]) => x !== void 0)) : v;
|
|
1076
|
+
return {
|
|
1077
|
+
...params,
|
|
1078
|
+
context: snap(params.context),
|
|
1079
|
+
bindings: snap(params.bindings),
|
|
1080
|
+
sessionFrom: Array.isArray(params.sessionFrom) ? [...params.sessionFrom] : params.sessionFrom
|
|
1081
|
+
};
|
|
1082
|
+
}
|
|
1083
|
+
function isPlainObject(v) {
|
|
1084
|
+
if (typeof v !== "object" || v === null || Array.isArray(v)) return false;
|
|
1085
|
+
const proto = Object.getPrototypeOf(v);
|
|
1086
|
+
return proto === Object.prototype || proto === null;
|
|
1087
|
+
}
|
|
1088
|
+
function isScalar(v) {
|
|
1089
|
+
return v === null || typeof v === "string" || typeof v === "boolean" || typeof v === "number" && Number.isFinite(v);
|
|
1090
|
+
}
|
|
1091
|
+
function validateStructuredInput(params) {
|
|
1092
|
+
if (params.consumerKey !== void 0 && (typeof params.consumerKey !== "string" || !params.consumerKey.trim())) {
|
|
1093
|
+
throw new ZihinAgentError("config", "`consumerKey` must be a non-empty string.");
|
|
1094
|
+
}
|
|
1095
|
+
if (typeof params.consumerKey === "string" && params.consumerKey.length > MAX_CONSUMER_KEY_CHARS) {
|
|
1096
|
+
throw new ZihinAgentError("config", `\`consumerKey\` is longer than ${MAX_CONSUMER_KEY_CHARS} characters.`);
|
|
1097
|
+
}
|
|
1098
|
+
for (const name of ["context", "bindings"]) {
|
|
1099
|
+
const v = params[name];
|
|
1100
|
+
if (v === void 0) continue;
|
|
1101
|
+
if (!isPlainObject(v)) {
|
|
1102
|
+
throw new ZihinAgentError("config", `\`${name}\` must be a flat plain object.`);
|
|
1103
|
+
}
|
|
1104
|
+
const entries = Object.entries(v);
|
|
1105
|
+
if (entries.length > MAX_FIELDS) {
|
|
1106
|
+
throw new ZihinAgentError("config", `\`${name}\` accepts at most ${MAX_FIELDS} fields.`);
|
|
1107
|
+
}
|
|
1108
|
+
for (const [k, val] of entries) {
|
|
1109
|
+
if (!FIELD_NAME_RE.test(k) || PROTOTYPE_KEYS.has(k)) {
|
|
1110
|
+
throw new ZihinAgentError(
|
|
1111
|
+
"config",
|
|
1112
|
+
`Invalid field name in \`${name}\`: use letters, digits, \`_\` or \`-\`, starting with a letter or \`_\` (max 64 characters; \`constructor\`, \`prototype\` and \`__proto__\` are not allowed).`
|
|
1113
|
+
);
|
|
1114
|
+
}
|
|
1115
|
+
if (typeof val === "string" && val.length > MAX_VALUE_CHARS) {
|
|
1116
|
+
throw new ZihinAgentError("config", `\`${name}.${k}\` is longer than ${MAX_VALUE_CHARS} characters.`);
|
|
1117
|
+
}
|
|
1118
|
+
if (!isScalar(val)) {
|
|
1119
|
+
throw new ZihinAgentError(
|
|
1120
|
+
"config",
|
|
1121
|
+
`\`${name}.${k}\` must be a string, number, boolean or null \u2014 \`${name}\` is flat (no nested objects or arrays).`
|
|
1122
|
+
);
|
|
1123
|
+
}
|
|
1124
|
+
}
|
|
1125
|
+
if (JSON.stringify(v).length > MAX_TOTAL_CHARS[name]) {
|
|
1126
|
+
throw new ZihinAgentError("config", `\`${name}\` is longer than ${MAX_TOTAL_CHARS[name]} characters in total (as JSON).`);
|
|
1127
|
+
}
|
|
1128
|
+
}
|
|
1129
|
+
if (params.sessionFrom !== void 0) {
|
|
1130
|
+
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))) {
|
|
1131
|
+
throw new ZihinAgentError("config", `\`sessionFrom\` must be an array of up to ${MAX_SESSION_FROM_FIELDS} field names.`);
|
|
1132
|
+
}
|
|
1133
|
+
if (params.sessionFrom.length && params.sessionId) {
|
|
1134
|
+
throw new ZihinAgentError("config", "Use either `sessionId` or `sessionFrom`, not both.");
|
|
1135
|
+
}
|
|
1136
|
+
const available = { ...params.context, ...params.bindings };
|
|
1137
|
+
const has = (f) => Object.prototype.hasOwnProperty.call(available, f) && Boolean(available[f]);
|
|
1138
|
+
if (params.sessionFrom.length && !params.sessionFrom.some(has)) {
|
|
1139
|
+
throw new ZihinAgentError(
|
|
1140
|
+
"config",
|
|
1141
|
+
`\`sessionFrom\` names ${params.sessionFrom.map((f) => `"${f}"`).join(", ")}, but none of them has a value in \`context\` or \`bindings\`.`
|
|
1142
|
+
);
|
|
1143
|
+
}
|
|
1144
|
+
}
|
|
1145
|
+
}
|
|
1146
|
+
function requestedInputFields(params) {
|
|
1147
|
+
const out = [];
|
|
1148
|
+
if (params.context) out.push("context");
|
|
1149
|
+
if (params.bindings) out.push("bindings");
|
|
1150
|
+
if (params.sessionFrom?.length) out.push("session_from");
|
|
1151
|
+
return out;
|
|
1152
|
+
}
|
|
1153
|
+
function checkInputCapability(params, chunk) {
|
|
1154
|
+
const requested = requestedInputFields(params);
|
|
1155
|
+
const ignored = chunk?.ignoredFields ?? [];
|
|
1156
|
+
const sent = [...requested, ...params.consumerKey ? ["consumer_key"] : []];
|
|
1157
|
+
const blocking = ignored.filter(
|
|
1158
|
+
(f) => f.reason !== "redacted" && sent.some((name) => f.field === name || f.field.startsWith(`${name}.`))
|
|
1159
|
+
);
|
|
1160
|
+
const tail = "the stream was closed to cancel the turn, but the message may already be recorded in the session.";
|
|
1161
|
+
if (blocking.length) {
|
|
1162
|
+
const shown = blocking.slice(0, 5);
|
|
1163
|
+
const more = blocking.length > shown.length ? ` (+${blocking.length - shown.length} more)` : "";
|
|
1164
|
+
const what = shown.map((f) => f.reason ? `\`${f.field}\` (${f.reason})` : `\`${f.field}\``).join(", ") + more;
|
|
1165
|
+
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." : "";
|
|
1166
|
+
throw new ZihinAgentError("config", `The server ignored ${what}.${hint} The answer is not used: ${tail}`, {
|
|
1167
|
+
requestId: chunk?.requestId
|
|
1168
|
+
});
|
|
1169
|
+
}
|
|
1170
|
+
if (requested.length && chunk?.acceptedFields === void 0) {
|
|
1171
|
+
throw new ZihinAgentError(
|
|
1172
|
+
"config",
|
|
1173
|
+
`This Zihin server does not support ${requested.map((f) => `\`${f}\``).join(", ")} on /stream yet (no \`accepted_fields\` in the stream metadata) \u2014 ${tail}`,
|
|
1174
|
+
{ requestId: chunk?.requestId }
|
|
1175
|
+
);
|
|
1176
|
+
}
|
|
1177
|
+
const mustAccept = chunk?.acceptedFields !== void 0 && params.consumerKey ? [...requested, "consumer_key"] : requested;
|
|
1178
|
+
const notAccepted = mustAccept.filter((f) => !chunk?.acceptedFields?.includes(f));
|
|
1179
|
+
if (notAccepted.length) {
|
|
1180
|
+
throw new ZihinAgentError(
|
|
1181
|
+
"config",
|
|
1182
|
+
`The server did not accept ${notAccepted.map((f) => `\`${f}\``).join(", ")} (missing from \`accepted_fields\`) \u2014 ${tail}`,
|
|
1183
|
+
{ requestId: chunk?.requestId }
|
|
1184
|
+
);
|
|
1185
|
+
}
|
|
1186
|
+
}
|
|
1187
|
+
var TURN_EVENTS = /* @__PURE__ */ new Set(["phase", "thinking", "tool", "action_proposed", "token", "response", "metrics", "done"]);
|
|
1188
|
+
async function* streamAgent(cfg, rawParams) {
|
|
1189
|
+
const params = snapshotStructuredInput(rawParams);
|
|
1015
1190
|
validateParams(params);
|
|
1016
1191
|
const callerTimeoutMs = params.options?.timeoutMs !== void 0 ? normalizeTimeoutMs(params.options.timeoutMs, "options.timeoutMs") : void 0;
|
|
1017
1192
|
const timeoutMs = callerTimeoutMs !== void 0 ? callerTimeoutMs + DEADLINE_GRACE_MS : resolveTimeout(cfg, "stream");
|
|
1018
1193
|
const reportedMs = callerTimeoutMs ?? timeoutMs;
|
|
1019
1194
|
const url = `${cfg.baseUrl}/api/v2/agents/${encodeURIComponent(params.agentId)}/stream`;
|
|
1195
|
+
let needsCapability = requestedInputFields(params).length > 0 || !!params.consumerKey;
|
|
1196
|
+
let gated = false;
|
|
1020
1197
|
const { response, deadline, endpoint } = await openStream({
|
|
1021
1198
|
url,
|
|
1022
1199
|
headers: buildHeaders(cfg.apiKey),
|
|
@@ -1044,7 +1221,14 @@ async function* streamAgent(cfg, params) {
|
|
|
1044
1221
|
maxEventBytes: cfg.maxStreamEventBytes,
|
|
1045
1222
|
maxTotalBytes: cfg.maxStreamTotalBytes
|
|
1046
1223
|
})) {
|
|
1047
|
-
|
|
1224
|
+
const chunk = mapEvent(e);
|
|
1225
|
+
if (needsCapability) {
|
|
1226
|
+
if (chunk.type === "metadata") checkInputCapability(params, chunk);
|
|
1227
|
+
else if (chunk.type === "status" && chunk.phase === "GATED") gated = true;
|
|
1228
|
+
else if (TURN_EVENTS.has(chunk.type) && !gated) checkInputCapability(params, void 0);
|
|
1229
|
+
if (chunk.type === "metadata" || TURN_EVENTS.has(chunk.type) && !gated) needsCapability = false;
|
|
1230
|
+
}
|
|
1231
|
+
yield chunk;
|
|
1048
1232
|
}
|
|
1049
1233
|
} finally {
|
|
1050
1234
|
deadline.dispose();
|
|
@@ -1052,6 +1236,7 @@ async function* streamAgent(cfg, params) {
|
|
|
1052
1236
|
}
|
|
1053
1237
|
function classifyStreamErrorKind(code, timedOut) {
|
|
1054
1238
|
if (timedOut === true) return "timeout";
|
|
1239
|
+
if (code === "VALIDATION_ERROR") return "config";
|
|
1055
1240
|
if (timedOut === false) return "server";
|
|
1056
1241
|
return /TIMEOUT/i.test(code) ? "timeout" : "server";
|
|
1057
1242
|
}
|
|
@@ -1064,6 +1249,77 @@ function errorFromStreamChunk(chunk) {
|
|
|
1064
1249
|
retryable: false
|
|
1065
1250
|
});
|
|
1066
1251
|
}
|
|
1252
|
+
var FAILED_OUTCOMES = {
|
|
1253
|
+
ERROR: { kind: "server", message: "The agent turn failed on the server; its text is the engine failure notice, not an answer." },
|
|
1254
|
+
// `server`, não `aborted`: `aborted` é o cancelamento do PRÓPRIO chamador, que `onError` manda
|
|
1255
|
+
// filtrar — um cancelamento feito pelo operador sumiria junto.
|
|
1256
|
+
CANCELLED: { kind: "server", message: "The agent turn was cancelled on the server (by an operator or a platform guard)." },
|
|
1257
|
+
// Modo processo drenando/encerrando; o motor retoma depois. Nenhum canal atual o produz.
|
|
1258
|
+
SUSPENDED: { kind: "blocked", message: "The agent turn was suspended by the server and did not answer; retry later." },
|
|
1259
|
+
BUDGET_EXCEEDED: { kind: "blocked", message: "The agent's spending cap was reached; raise the agent budget to keep invoking it." },
|
|
1260
|
+
QUOTA_EXCEEDED: { kind: "quota_exceeded", message: "Your Zihin token quota is exhausted. Upgrade the plan or wait for the next cycle." },
|
|
1261
|
+
SESSION_BUSY: {
|
|
1262
|
+
kind: "blocked",
|
|
1263
|
+
message: "Another turn is still running in this session; retry after it finishes.",
|
|
1264
|
+
retryable: true
|
|
1265
|
+
},
|
|
1266
|
+
// Hoje a rota manda CONFLICT como `event: error` (não chega ao `done`); mapeado por defesa.
|
|
1267
|
+
CONFLICT: {
|
|
1268
|
+
kind: "blocked",
|
|
1269
|
+
message: "The declared consumer identity does not match the one already bound to this session."
|
|
1270
|
+
}
|
|
1271
|
+
};
|
|
1272
|
+
var PAUSED_BLOCKS = /* @__PURE__ */ new Set(["agent_paused", "agent_archived"]);
|
|
1273
|
+
var ERROR_BLOCKS = {
|
|
1274
|
+
ownership_denied: {
|
|
1275
|
+
kind: "not_found",
|
|
1276
|
+
message: "Session not found for this API key \u2014 start a new conversation (omit sessionId) instead of reusing this one."
|
|
1277
|
+
},
|
|
1278
|
+
session_load_failed: { kind: "blocked", message: "The server could not load this conversation right now; retry shortly.", retryable: true },
|
|
1279
|
+
approval_resolve_failed: { kind: "blocked", message: "The server could not route the required approval right now; retry shortly.", retryable: true },
|
|
1280
|
+
approval_no_approver: { kind: "blocked", message: "This action requires approval, but the approval policy has no approver configured." },
|
|
1281
|
+
blocked_schedule: { kind: "blocked", message: "The agent is outside its allowed schedule (security policy)." },
|
|
1282
|
+
owner_inactive: { kind: "blocked", message: "The agent's owner is inactive; the agent cannot be invoked." }
|
|
1283
|
+
};
|
|
1284
|
+
function own(table, key) {
|
|
1285
|
+
return key !== void 0 && Object.prototype.hasOwnProperty.call(table, key) ? table[key] : void 0;
|
|
1286
|
+
}
|
|
1287
|
+
function errorFromDoneChunk(chunk, ctx = {}) {
|
|
1288
|
+
const meta = { executionId: ctx.executionId, sessionId: ctx.sessionId };
|
|
1289
|
+
const blockedReason = safeLabel(ctx.blocked);
|
|
1290
|
+
if (chunk.outcome === "TIMEOUT" || chunk.timedOut === true) {
|
|
1291
|
+
const clock = chunk.timeoutClock;
|
|
1292
|
+
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";
|
|
1293
|
+
return new ZihinAgentError("timeout", `The agent turn timed out (${what}).`, {
|
|
1294
|
+
...meta,
|
|
1295
|
+
code: "TURN_TIMEOUT",
|
|
1296
|
+
retryable: false,
|
|
1297
|
+
timeoutClock: clock
|
|
1298
|
+
});
|
|
1299
|
+
}
|
|
1300
|
+
if (chunk.outcome === "PAUSED" && blockedReason && PAUSED_BLOCKS.has(blockedReason)) {
|
|
1301
|
+
return new ZihinAgentError("blocked", "The agent is paused or archived and did not answer.", {
|
|
1302
|
+
...meta,
|
|
1303
|
+
code: "PAUSED",
|
|
1304
|
+
retryable: false
|
|
1305
|
+
});
|
|
1306
|
+
}
|
|
1307
|
+
if (chunk.outcome === "ERROR" && blockedReason) {
|
|
1308
|
+
const gate = own(ERROR_BLOCKS, blockedReason);
|
|
1309
|
+
return new ZihinAgentError(gate?.kind ?? "blocked", gate?.message ?? `The agent turn was blocked by the server (${blockedReason}).`, {
|
|
1310
|
+
...meta,
|
|
1311
|
+
code: blockedReason.toUpperCase(),
|
|
1312
|
+
retryable: gate?.retryable ?? false
|
|
1313
|
+
});
|
|
1314
|
+
}
|
|
1315
|
+
const failed = own(FAILED_OUTCOMES, chunk.outcome);
|
|
1316
|
+
if (!failed) return void 0;
|
|
1317
|
+
return new ZihinAgentError(failed.kind, failed.message, {
|
|
1318
|
+
...meta,
|
|
1319
|
+
code: chunk.outcome,
|
|
1320
|
+
retryable: failed.retryable ?? false
|
|
1321
|
+
});
|
|
1322
|
+
}
|
|
1067
1323
|
async function invokeAgent(cfg, params) {
|
|
1068
1324
|
const raw = [];
|
|
1069
1325
|
const keepRaw = params.includeRaw === true;
|
|
@@ -1071,7 +1327,10 @@ async function invokeAgent(cfg, params) {
|
|
|
1071
1327
|
let metricsChunk;
|
|
1072
1328
|
let metadataSessionId;
|
|
1073
1329
|
let mintedSessionId;
|
|
1074
|
-
let
|
|
1330
|
+
let doneChunk;
|
|
1331
|
+
let blocked;
|
|
1332
|
+
let acceptedFields;
|
|
1333
|
+
let ignoredFields;
|
|
1075
1334
|
let tokenBuffer = "";
|
|
1076
1335
|
for await (const chunk of streamAgent(cfg, params)) {
|
|
1077
1336
|
if (keepRaw) raw.push(chunk);
|
|
@@ -1081,10 +1340,19 @@ async function invokeAgent(cfg, params) {
|
|
|
1081
1340
|
if (chunk.type === "response") responseChunk = chunk;
|
|
1082
1341
|
else if (chunk.type === "metrics") metricsChunk = chunk;
|
|
1083
1342
|
else if (chunk.type === "token") tokenBuffer += chunk.content;
|
|
1084
|
-
else if (chunk.type === "metadata"
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1343
|
+
else if (chunk.type === "metadata") {
|
|
1344
|
+
if (chunk.sessionId) metadataSessionId = chunk.sessionId;
|
|
1345
|
+
acceptedFields = chunk.acceptedFields;
|
|
1346
|
+
ignoredFields = chunk.ignoredFields;
|
|
1347
|
+
} else if (chunk.type === "session" && chunk.sessionId) mintedSessionId = chunk.sessionId;
|
|
1348
|
+
else if (chunk.type === "done") doneChunk = chunk;
|
|
1349
|
+
else if (chunk.type === "status" && chunk.blocked) blocked = chunk.blocked;
|
|
1350
|
+
}
|
|
1351
|
+
const sessionId = mintedSessionId ?? responseChunk?.sessionId ?? metadataSessionId ?? params.sessionId;
|
|
1352
|
+
const executionId = metricsChunk?.executionId;
|
|
1353
|
+
const outcomeError = doneChunk && errorFromDoneChunk(doneChunk, { executionId, sessionId, blocked });
|
|
1354
|
+
if (outcomeError) throw outcomeError;
|
|
1355
|
+
const cleanEnd = doneChunk !== void 0;
|
|
1088
1356
|
if (!responseChunk) {
|
|
1089
1357
|
if (cleanEnd) {
|
|
1090
1358
|
throw new ZihinAgentError("protocol", "Stream ended (done) without a `response` event.");
|
|
@@ -1093,11 +1361,14 @@ async function invokeAgent(cfg, params) {
|
|
|
1093
1361
|
return {
|
|
1094
1362
|
content: tokenBuffer,
|
|
1095
1363
|
contentSource: "tokens",
|
|
1096
|
-
sessionId
|
|
1364
|
+
sessionId,
|
|
1097
1365
|
usage: metricsChunk?.usage,
|
|
1098
1366
|
model: metricsChunk?.model,
|
|
1099
1367
|
iterations: metricsChunk?.iterations,
|
|
1100
1368
|
executionTimeMs: metricsChunk?.executionTimeMs,
|
|
1369
|
+
executionId,
|
|
1370
|
+
acceptedFields,
|
|
1371
|
+
ignoredFields,
|
|
1101
1372
|
raw
|
|
1102
1373
|
};
|
|
1103
1374
|
}
|
|
@@ -1106,13 +1377,20 @@ async function invokeAgent(cfg, params) {
|
|
|
1106
1377
|
return {
|
|
1107
1378
|
content: responseChunk.content,
|
|
1108
1379
|
contentSource: "response",
|
|
1109
|
-
sessionId
|
|
1380
|
+
sessionId,
|
|
1110
1381
|
model: metricsChunk?.model,
|
|
1111
1382
|
usage: metricsChunk?.usage,
|
|
1112
1383
|
sources: responseChunk.sources,
|
|
1113
1384
|
blocks: responseChunk.blocks,
|
|
1385
|
+
gated: responseChunk.gated,
|
|
1114
1386
|
iterations: metricsChunk?.iterations,
|
|
1115
1387
|
executionTimeMs: metricsChunk?.executionTimeMs,
|
|
1388
|
+
executionId,
|
|
1389
|
+
outcome: doneChunk?.outcome,
|
|
1390
|
+
truncated: doneChunk?.truncated,
|
|
1391
|
+
finishReason: doneChunk?.finishReason,
|
|
1392
|
+
acceptedFields,
|
|
1393
|
+
ignoredFields,
|
|
1116
1394
|
raw
|
|
1117
1395
|
};
|
|
1118
1396
|
}
|
|
@@ -1719,6 +1997,7 @@ function signRequest(payload, secretKey, timestamp) {
|
|
|
1719
1997
|
ZihinAgentError,
|
|
1720
1998
|
classifyStreamErrorKind,
|
|
1721
1999
|
createClient,
|
|
2000
|
+
errorFromDoneChunk,
|
|
1722
2001
|
errorFromStreamChunk,
|
|
1723
2002
|
isCommercialBlock,
|
|
1724
2003
|
sanitizeApiError,
|