@channlize/core 0.5.0 → 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/dist/index.js CHANGED
@@ -31,6 +31,9 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
31
31
  var index_exports = {};
32
32
  __export(index_exports, {
33
33
  AGENT_DELIVERY_MODES: () => AGENT_DELIVERY_MODES,
34
+ AGENT_EXPORT_FORMATS: () => AGENT_EXPORT_FORMATS,
35
+ AGENT_EXPORT_KINDS: () => AGENT_EXPORT_KINDS,
36
+ AGENT_EXPORT_SYNC_MAX_ROWS: () => AGENT_EXPORT_SYNC_MAX_ROWS,
34
37
  AGENT_MESSAGE_TYPES: () => AGENT_MESSAGE_TYPES,
35
38
  AGENT_POLICY_CODES: () => AGENT_POLICY_CODES,
36
39
  AGENT_STATUSES: () => AGENT_STATUSES,
@@ -40,6 +43,7 @@ __export(index_exports, {
40
43
  AgentTaskError: () => AgentTaskError,
41
44
  AgentThreadPausedError: () => AgentThreadPausedError,
42
45
  AuthenticationError: () => AuthenticationError,
46
+ COST_GROUPINGS: () => COST_GROUPINGS,
43
47
  ChannlizeClient: () => ChannlizeClient,
44
48
  ChannlizeError: () => ChannlizeError,
45
49
  DEFAULT_BASE_URL: () => DEFAULT_BASE_URL,
@@ -71,9 +75,11 @@ __export(index_exports, {
71
75
  parseAgentWebhook: () => parseAgentWebhook,
72
76
  parseAgentWebhookEvent: () => parseAgentWebhookEvent,
73
77
  parseApiError: () => parseApiError,
78
+ parseTelemetryWebhook: () => parseTelemetryWebhook,
74
79
  readChannlizeEnv: () => readChannlizeEnv,
75
80
  retryAfter: () => retryAfter,
76
- verifyAgentWebhook: () => verifyAgentWebhook
81
+ verifyAgentWebhook: () => verifyAgentWebhook,
82
+ verifyTelemetryWebhook: () => verifyTelemetryWebhook
77
83
  });
78
84
  module.exports = __toCommonJS(index_exports);
79
85
 
@@ -260,6 +266,21 @@ function retryAfter(error, now = Date.now()) {
260
266
 
261
267
  // src/http.ts
262
268
  async function requestJson(http, endpoint, options = {}) {
269
+ return send(
270
+ http,
271
+ endpoint,
272
+ options,
273
+ async (res) => await res.json()
274
+ );
275
+ }
276
+ async function requestText(http, endpoint, options = {}) {
277
+ return send(http, endpoint, options, async (res) => ({
278
+ body: await res.text(),
279
+ status: res.status,
280
+ headers: res.headers
281
+ }));
282
+ }
283
+ async function send(http, endpoint, options, read) {
263
284
  const url = `${http.baseUrl}${endpoint}`;
264
285
  const controller = new AbortController();
265
286
  const timeoutId = setTimeout(() => controller.abort(), http.timeout);
@@ -284,8 +305,7 @@ async function requestJson(http, endpoint, options = {}) {
284
305
  if (!response.ok) {
285
306
  await handleErrorResponse(response);
286
307
  }
287
- const data = await response.json();
288
- return data;
308
+ return await read(response);
289
309
  } catch (error) {
290
310
  clearTimeout(timeoutId);
291
311
  if (error instanceof ChannlizeError) {
@@ -927,6 +947,270 @@ function isTaskHandoffMessage(message) {
927
947
  );
928
948
  }
929
949
 
950
+ // src/agents-observability.ts
951
+ var enc2 = encodeURIComponent;
952
+ function day(v) {
953
+ return v instanceof Date ? v.toISOString().slice(0, 10) : v;
954
+ }
955
+ function iso2(v) {
956
+ return v instanceof Date ? v.toISOString() : v;
957
+ }
958
+ function list(v) {
959
+ return Array.isArray(v) ? v.join(",") : v;
960
+ }
961
+ function need2(value, what, field) {
962
+ if (typeof value !== "string" || !value) {
963
+ throw new ValidationError(`${what} is required`, field);
964
+ }
965
+ }
966
+ function exportFilterFields(f = {}) {
967
+ return {
968
+ from: iso2(f.from),
969
+ to: iso2(f.to),
970
+ agent_id: f.agentId,
971
+ channel_id: f.channelId,
972
+ thread_id: f.threadId,
973
+ type: f.type,
974
+ status: list(f.status),
975
+ action: f.action,
976
+ actor_type: f.actorType,
977
+ actor_id: f.actorId,
978
+ target_type: f.targetType,
979
+ target_id: f.targetId
980
+ };
981
+ }
982
+ function defined(o) {
983
+ return Object.fromEntries(
984
+ Object.entries(o).filter(([, v]) => v !== void 0)
985
+ );
986
+ }
987
+ function filenameOf(disposition) {
988
+ const m = disposition ? /filename="?([^";]+)"?/i.exec(disposition) : null;
989
+ return m ? m[1] : null;
990
+ }
991
+ async function* paginate(page, max) {
992
+ const limit = max ?? Infinity;
993
+ if (limit <= 0) return;
994
+ let cursor;
995
+ let seen = 0;
996
+ for (; ; ) {
997
+ const r = await page(cursor);
998
+ for (const item of r.items) {
999
+ yield item;
1000
+ if (++seen >= limit) return;
1001
+ }
1002
+ if (!r.nextCursor || r.nextCursor === cursor) return;
1003
+ cursor = r.nextCursor;
1004
+ }
1005
+ }
1006
+ function createAgentsObservability(request, requestText2) {
1007
+ const safely = (fn) => {
1008
+ try {
1009
+ return fn();
1010
+ } catch (error) {
1011
+ return Promise.reject(error);
1012
+ }
1013
+ };
1014
+ const rangeQuery = (f = {}) => ({
1015
+ from: day(f.from),
1016
+ to: day(f.to),
1017
+ channel_id: f.channelId,
1018
+ agent_id: f.agentId
1019
+ });
1020
+ function edge(source, target, options = {}) {
1021
+ return safely(async () => {
1022
+ need2(source, "The source node id", "source");
1023
+ need2(target, "The target node id", "target");
1024
+ const r = await request("GET", "/v1/agents/insights/graph/edge", {
1025
+ query: {
1026
+ source,
1027
+ target,
1028
+ from: day(options.from),
1029
+ to: day(options.to),
1030
+ channel_id: options.channelId,
1031
+ cursor: options.cursor,
1032
+ limit: options.limit
1033
+ }
1034
+ });
1035
+ return {
1036
+ range: r.range,
1037
+ source: r.source,
1038
+ target: r.target,
1039
+ threads: r.threads ?? [],
1040
+ nextCursor: r.next_cursor ?? null
1041
+ };
1042
+ });
1043
+ }
1044
+ function search(options) {
1045
+ return safely(async () => {
1046
+ if (!options || typeof options.q !== "string" || options.q.trim().length < 2) {
1047
+ throw new ValidationError("search needs q, at least 2 characters", "q");
1048
+ }
1049
+ const r = await request("GET", "/v1/agents/search", {
1050
+ query: {
1051
+ q: options.q,
1052
+ agent_id: options.agentId,
1053
+ channel_id: options.channelId,
1054
+ type: options.type,
1055
+ include: list(options.include),
1056
+ from: iso2(options.from),
1057
+ to: iso2(options.to),
1058
+ cursor: options.cursor,
1059
+ limit: options.limit
1060
+ }
1061
+ });
1062
+ return {
1063
+ results: r.results ?? [],
1064
+ nextCursor: r.next_cursor ?? null,
1065
+ window: r.window
1066
+ };
1067
+ });
1068
+ }
1069
+ return {
1070
+ insights: {
1071
+ graph(filters = {}) {
1072
+ return request("GET", "/v1/agents/insights/graph", {
1073
+ query: {
1074
+ ...rangeQuery(filters),
1075
+ team_id: filters.teamId,
1076
+ limit: filters.limit,
1077
+ edge_limit: filters.edgeLimit
1078
+ }
1079
+ });
1080
+ },
1081
+ edge,
1082
+ edgeAll(source, target, options = {}) {
1083
+ const { max, ...rest } = options;
1084
+ return paginate(async (cursor) => {
1085
+ const page = await edge(source, target, { ...rest, cursor });
1086
+ return { items: page.threads, nextCursor: page.nextCursor };
1087
+ }, max);
1088
+ },
1089
+ cost(options = {}) {
1090
+ return request("GET", "/v1/agents/insights/cost", {
1091
+ query: {
1092
+ ...rangeQuery(options),
1093
+ group_by: options.groupBy,
1094
+ team_id: options.teamId,
1095
+ limit: options.limit
1096
+ }
1097
+ });
1098
+ },
1099
+ metrics(filters = {}) {
1100
+ return request("GET", "/v1/agents/insights/metrics", {
1101
+ query: rangeQuery(filters)
1102
+ });
1103
+ }
1104
+ },
1105
+ search,
1106
+ searchAll(options) {
1107
+ const { max, ...rest } = options ?? {};
1108
+ return paginate(async (cursor) => {
1109
+ const page = await search({ ...rest, cursor });
1110
+ return { items: page.results, nextCursor: page.nextCursor };
1111
+ }, max);
1112
+ },
1113
+ exports: {
1114
+ download(kind, format = "csv", filters = {}) {
1115
+ return safely(async () => {
1116
+ need2(kind, "The export kind", "kind");
1117
+ const res = await requestText2("/v1/agents/export", {
1118
+ query: { kind, format, ...exportFilterFields(filters) }
1119
+ });
1120
+ return {
1121
+ body: res.body,
1122
+ rows: Number(res.headers.get("x-export-rows") ?? 0) || 0,
1123
+ truncated: res.headers.get("x-export-truncated") === "true",
1124
+ contentType: res.headers.get("content-type") ?? "",
1125
+ filename: filenameOf(res.headers.get("content-disposition"))
1126
+ };
1127
+ });
1128
+ },
1129
+ start(input) {
1130
+ return safely(async () => {
1131
+ need2(input?.kind, "The export kind", "kind");
1132
+ const r = await request("POST", "/v1/agents/export", {
1133
+ body: {
1134
+ kind: input.kind,
1135
+ format: input.format,
1136
+ filters: defined(exportFilterFields(input.filters))
1137
+ }
1138
+ });
1139
+ return { exportId: r.export_id, status: r.status, export: r.export };
1140
+ });
1141
+ },
1142
+ get(exportId) {
1143
+ return safely(async () => {
1144
+ need2(exportId, "The export id", "exportId");
1145
+ const r = await request(
1146
+ "GET",
1147
+ `/v1/agents/export/${enc2(exportId)}`
1148
+ );
1149
+ return r.export;
1150
+ });
1151
+ },
1152
+ async list(options = {}) {
1153
+ const r = await request(
1154
+ "GET",
1155
+ "/v1/agents/export",
1156
+ { query: { limit: options.limit } }
1157
+ );
1158
+ return r.exports ?? [];
1159
+ }
1160
+ },
1161
+ telemetry: {
1162
+ async getOtel() {
1163
+ return (await request("GET", "/v1/agents/telemetry/otel")).otel;
1164
+ },
1165
+ async setOtel(input) {
1166
+ const r = await request(
1167
+ "PUT",
1168
+ "/v1/agents/telemetry/otel",
1169
+ {
1170
+ body: {
1171
+ enabled: input?.enabled,
1172
+ endpoint_url: input?.endpointUrl,
1173
+ headers: input?.headers
1174
+ }
1175
+ }
1176
+ );
1177
+ return r.otel;
1178
+ },
1179
+ testOtel() {
1180
+ return request(
1181
+ "POST",
1182
+ "/v1/agents/telemetry/otel/test"
1183
+ );
1184
+ },
1185
+ async getWebhook() {
1186
+ return (await request(
1187
+ "GET",
1188
+ "/v1/agents/telemetry/webhook"
1189
+ )).webhook;
1190
+ },
1191
+ setWebhook(input) {
1192
+ return request(
1193
+ "PUT",
1194
+ "/v1/agents/telemetry/webhook",
1195
+ {
1196
+ body: {
1197
+ enabled: input?.enabled,
1198
+ url: input?.url,
1199
+ rotate_secret: input?.rotateSecret
1200
+ }
1201
+ }
1202
+ );
1203
+ },
1204
+ testWebhook() {
1205
+ return request(
1206
+ "POST",
1207
+ "/v1/agents/telemetry/webhook/test"
1208
+ );
1209
+ }
1210
+ }
1211
+ };
1212
+ }
1213
+
930
1214
  // src/agents.ts
931
1215
  function resolve(config) {
932
1216
  const env = readChannlizeEnv();
@@ -953,7 +1237,7 @@ function resolve(config) {
953
1237
  timeout: config.timeout ?? DEFAULT_TIMEOUT
954
1238
  };
955
1239
  }
956
- var enc2 = encodeURIComponent;
1240
+ var enc3 = encodeURIComponent;
957
1241
  function isFatal(error) {
958
1242
  return error instanceof ChannlizeError && [400, 401, 403, 404].includes(error.statusCode ?? 0);
959
1243
  }
@@ -986,6 +1270,17 @@ function createAgentsClient(config = {}) {
986
1270
  });
987
1271
  return res;
988
1272
  };
1273
+ const fetchText = (path, options = {}) => {
1274
+ const params = new URLSearchParams();
1275
+ for (const [k, v] of Object.entries(options.query ?? {})) {
1276
+ if (v !== void 0 && v !== "") params.set(k, String(v));
1277
+ }
1278
+ const qs = params.toString();
1279
+ return requestText(cfg, `${path}${qs ? `?${qs}` : ""}`, {
1280
+ method: "GET",
1281
+ headers: { Authorization: `Bearer ${cfg.apiKey}` }
1282
+ });
1283
+ };
989
1284
  function agentFor(agentId, what) {
990
1285
  const id = agentId ?? cfg.agentId;
991
1286
  if (!id) {
@@ -998,7 +1293,7 @@ function createAgentsClient(config = {}) {
998
1293
  }
999
1294
  async function inbox(agentId, options = {}) {
1000
1295
  const id = agentFor(agentId, "inbox");
1001
- const r = await request("GET", `/v1/agents/${enc2(id)}/inbox`, {
1296
+ const r = await request("GET", `/v1/agents/${enc3(id)}/inbox`, {
1002
1297
  query: { cursor: options.cursor, limit: options.limit }
1003
1298
  });
1004
1299
  return {
@@ -1018,7 +1313,7 @@ function createAgentsClient(config = {}) {
1018
1313
  "ids"
1019
1314
  );
1020
1315
  }
1021
- return request("POST", `/v1/agents/${enc2(id)}/inbox/ack`, {
1316
+ return request("POST", `/v1/agents/${enc3(id)}/inbox/ack`, {
1022
1317
  body
1023
1318
  });
1024
1319
  }
@@ -1073,6 +1368,7 @@ function createAgentsClient(config = {}) {
1073
1368
  });
1074
1369
  return {
1075
1370
  ...createAgentsGovernance(request, cfg.agentId),
1371
+ ...createAgentsObservability(request, fetchText),
1076
1372
  agentId: cfg.agentId,
1077
1373
  send(input) {
1078
1374
  if (!input || typeof input.channel !== "string" || !input.channel) {
@@ -1106,7 +1402,7 @@ function createAgentsClient(config = {}) {
1106
1402
  return r.agents ?? [];
1107
1403
  },
1108
1404
  get(agentId) {
1109
- return request("GET", `/v1/agents/${enc2(agentId)}`);
1405
+ return request("GET", `/v1/agents/${enc3(agentId)}`);
1110
1406
  },
1111
1407
  channels: {
1112
1408
  async list(options = {}) {
@@ -1118,20 +1414,20 @@ function createAgentsClient(config = {}) {
1118
1414
  return r.channels ?? [];
1119
1415
  },
1120
1416
  get(channelId, options) {
1121
- return request("GET", `/v1/agents/channels/${enc2(channelId)}`, {
1417
+ return request("GET", `/v1/agents/channels/${enc3(channelId)}`, {
1122
1418
  query: channelRead(options)
1123
1419
  });
1124
1420
  },
1125
1421
  async members(channelId, options) {
1126
1422
  const r = await request(
1127
1423
  "GET",
1128
- `/v1/agents/channels/${enc2(channelId)}/members`,
1424
+ `/v1/agents/channels/${enc3(channelId)}/members`,
1129
1425
  { query: channelRead(options) }
1130
1426
  );
1131
1427
  return r.members ?? [];
1132
1428
  },
1133
1429
  async messages(channelId, options = {}) {
1134
- const r = await request("GET", `/v1/agents/channels/${enc2(channelId)}/messages`, {
1430
+ const r = await request("GET", `/v1/agents/channels/${enc3(channelId)}/messages`, {
1135
1431
  query: {
1136
1432
  ...channelRead(options),
1137
1433
  cursor: options.cursor,
@@ -1167,7 +1463,7 @@ async function getSubtle() {
1167
1463
  } catch {
1168
1464
  }
1169
1465
  throw new ChannlizeError(
1170
- "verifyAgentWebhook needs the Web Crypto API (globalThis.crypto.subtle, or node:crypto webcrypto on Node.js 18)"
1466
+ "Webhook signature checks need the Web Crypto API (globalThis.crypto.subtle, or node:crypto webcrypto on Node.js 18)"
1171
1467
  );
1172
1468
  }
1173
1469
  function hexToBytes(hex) {
@@ -1189,6 +1485,9 @@ function toBytes(body) {
1189
1485
  async function verifyAgentWebhook(rawBody, signatureHeader, secret) {
1190
1486
  if (!secret)
1191
1487
  throw new ValidationError("The agent secret is required", "secret");
1488
+ return verifyHmacSha256(rawBody, signatureHeader, secret);
1489
+ }
1490
+ async function verifyHmacSha256(rawBody, signatureHeader, secret) {
1192
1491
  const header = Array.isArray(signatureHeader) ? signatureHeader[0] : signatureHeader;
1193
1492
  const m = /^sha256=([0-9a-f]+)$/i.exec(header?.trim() ?? "");
1194
1493
  const expected = m ? hexToBytes(m[1]) : null;
@@ -1221,6 +1520,26 @@ async function parseAgentWebhookEvent(rawBody, signatureHeader, secret) {
1221
1520
  const text = typeof rawBody === "string" ? rawBody : new TextDecoder().decode(rawBody);
1222
1521
  return JSON.parse(text);
1223
1522
  }
1523
+ async function verifyTelemetryWebhook(rawBody, signatureHeader, secret) {
1524
+ if (!secret) {
1525
+ throw new ValidationError(
1526
+ "The telemetry webhook secret is required",
1527
+ "secret"
1528
+ );
1529
+ }
1530
+ return verifyHmacSha256(rawBody, signatureHeader, secret);
1531
+ }
1532
+ async function parseTelemetryWebhook(rawBody, signatureHeader, secret) {
1533
+ if (!await verifyTelemetryWebhook(rawBody, signatureHeader, secret)) {
1534
+ throw new ChannlizeError(
1535
+ "Invalid telemetry webhook signature",
1536
+ 401,
1537
+ "INVALID_SIGNATURE"
1538
+ );
1539
+ }
1540
+ const text = typeof rawBody === "string" ? rawBody : new TextDecoder().decode(rawBody);
1541
+ return JSON.parse(text);
1542
+ }
1224
1543
 
1225
1544
  // src/client.ts
1226
1545
  var ChannlizeClient = class {
@@ -1414,9 +1733,9 @@ var ChannlizeClient = class {
1414
1733
  * plan's limit for the billing period) resolves with `success: false`
1415
1734
  * and `dropped` instead of throwing, with one console warning per client.
1416
1735
  */
1417
- async nonFatalTracking(send) {
1736
+ async nonFatalTracking(send2) {
1418
1737
  try {
1419
- return await send();
1738
+ return await send2();
1420
1739
  } catch (error) {
1421
1740
  if (!(error instanceof RateLimitError)) throw error;
1422
1741
  const dropped = error.code === "PLAN_LIMIT_REACHED" ? "plan_limit" : "rate_limit";
@@ -1498,9 +1817,29 @@ var AGENT_TASK_STATUSES = [
1498
1817
  "canceled",
1499
1818
  "rejected"
1500
1819
  ];
1820
+
1821
+ // src/agents-observability-types.ts
1822
+ var COST_GROUPINGS = [
1823
+ "agent",
1824
+ "channel",
1825
+ "team",
1826
+ "task",
1827
+ "day"
1828
+ ];
1829
+ var AGENT_EXPORT_KINDS = [
1830
+ "messages",
1831
+ "tasks",
1832
+ "questions",
1833
+ "audit"
1834
+ ];
1835
+ var AGENT_EXPORT_FORMATS = ["csv", "json"];
1836
+ var AGENT_EXPORT_SYNC_MAX_ROWS = 1e4;
1501
1837
  // Annotate the CommonJS export names for ESM import in node:
1502
1838
  0 && (module.exports = {
1503
1839
  AGENT_DELIVERY_MODES,
1840
+ AGENT_EXPORT_FORMATS,
1841
+ AGENT_EXPORT_KINDS,
1842
+ AGENT_EXPORT_SYNC_MAX_ROWS,
1504
1843
  AGENT_MESSAGE_TYPES,
1505
1844
  AGENT_POLICY_CODES,
1506
1845
  AGENT_STATUSES,
@@ -1510,6 +1849,7 @@ var AGENT_TASK_STATUSES = [
1510
1849
  AgentTaskError,
1511
1850
  AgentThreadPausedError,
1512
1851
  AuthenticationError,
1852
+ COST_GROUPINGS,
1513
1853
  ChannlizeClient,
1514
1854
  ChannlizeError,
1515
1855
  DEFAULT_BASE_URL,
@@ -1540,8 +1880,10 @@ var AGENT_TASK_STATUSES = [
1540
1880
  parseAgentWebhook,
1541
1881
  parseAgentWebhookEvent,
1542
1882
  parseApiError,
1883
+ parseTelemetryWebhook,
1543
1884
  readChannlizeEnv,
1544
1885
  retryAfter,
1545
- verifyAgentWebhook
1886
+ verifyAgentWebhook,
1887
+ verifyTelemetryWebhook
1546
1888
  });
1547
1889
  //# sourceMappingURL=index.js.map