@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.mjs CHANGED
@@ -181,6 +181,21 @@ function retryAfter(error, now = Date.now()) {
181
181
 
182
182
  // src/http.ts
183
183
  async function requestJson(http, endpoint, options = {}) {
184
+ return send(
185
+ http,
186
+ endpoint,
187
+ options,
188
+ async (res) => await res.json()
189
+ );
190
+ }
191
+ async function requestText(http, endpoint, options = {}) {
192
+ return send(http, endpoint, options, async (res) => ({
193
+ body: await res.text(),
194
+ status: res.status,
195
+ headers: res.headers
196
+ }));
197
+ }
198
+ async function send(http, endpoint, options, read) {
184
199
  const url = `${http.baseUrl}${endpoint}`;
185
200
  const controller = new AbortController();
186
201
  const timeoutId = setTimeout(() => controller.abort(), http.timeout);
@@ -205,8 +220,7 @@ async function requestJson(http, endpoint, options = {}) {
205
220
  if (!response.ok) {
206
221
  await handleErrorResponse(response);
207
222
  }
208
- const data = await response.json();
209
- return data;
223
+ return await read(response);
210
224
  } catch (error) {
211
225
  clearTimeout(timeoutId);
212
226
  if (error instanceof ChannlizeError) {
@@ -848,6 +862,270 @@ function isTaskHandoffMessage(message) {
848
862
  );
849
863
  }
850
864
 
865
+ // src/agents-observability.ts
866
+ var enc2 = encodeURIComponent;
867
+ function day(v) {
868
+ return v instanceof Date ? v.toISOString().slice(0, 10) : v;
869
+ }
870
+ function iso2(v) {
871
+ return v instanceof Date ? v.toISOString() : v;
872
+ }
873
+ function list(v) {
874
+ return Array.isArray(v) ? v.join(",") : v;
875
+ }
876
+ function need2(value, what, field) {
877
+ if (typeof value !== "string" || !value) {
878
+ throw new ValidationError(`${what} is required`, field);
879
+ }
880
+ }
881
+ function exportFilterFields(f = {}) {
882
+ return {
883
+ from: iso2(f.from),
884
+ to: iso2(f.to),
885
+ agent_id: f.agentId,
886
+ channel_id: f.channelId,
887
+ thread_id: f.threadId,
888
+ type: f.type,
889
+ status: list(f.status),
890
+ action: f.action,
891
+ actor_type: f.actorType,
892
+ actor_id: f.actorId,
893
+ target_type: f.targetType,
894
+ target_id: f.targetId
895
+ };
896
+ }
897
+ function defined(o) {
898
+ return Object.fromEntries(
899
+ Object.entries(o).filter(([, v]) => v !== void 0)
900
+ );
901
+ }
902
+ function filenameOf(disposition) {
903
+ const m = disposition ? /filename="?([^";]+)"?/i.exec(disposition) : null;
904
+ return m ? m[1] : null;
905
+ }
906
+ async function* paginate(page, max) {
907
+ const limit = max ?? Infinity;
908
+ if (limit <= 0) return;
909
+ let cursor;
910
+ let seen = 0;
911
+ for (; ; ) {
912
+ const r = await page(cursor);
913
+ for (const item of r.items) {
914
+ yield item;
915
+ if (++seen >= limit) return;
916
+ }
917
+ if (!r.nextCursor || r.nextCursor === cursor) return;
918
+ cursor = r.nextCursor;
919
+ }
920
+ }
921
+ function createAgentsObservability(request, requestText2) {
922
+ const safely = (fn) => {
923
+ try {
924
+ return fn();
925
+ } catch (error) {
926
+ return Promise.reject(error);
927
+ }
928
+ };
929
+ const rangeQuery = (f = {}) => ({
930
+ from: day(f.from),
931
+ to: day(f.to),
932
+ channel_id: f.channelId,
933
+ agent_id: f.agentId
934
+ });
935
+ function edge(source, target, options = {}) {
936
+ return safely(async () => {
937
+ need2(source, "The source node id", "source");
938
+ need2(target, "The target node id", "target");
939
+ const r = await request("GET", "/v1/agents/insights/graph/edge", {
940
+ query: {
941
+ source,
942
+ target,
943
+ from: day(options.from),
944
+ to: day(options.to),
945
+ channel_id: options.channelId,
946
+ cursor: options.cursor,
947
+ limit: options.limit
948
+ }
949
+ });
950
+ return {
951
+ range: r.range,
952
+ source: r.source,
953
+ target: r.target,
954
+ threads: r.threads ?? [],
955
+ nextCursor: r.next_cursor ?? null
956
+ };
957
+ });
958
+ }
959
+ function search(options) {
960
+ return safely(async () => {
961
+ if (!options || typeof options.q !== "string" || options.q.trim().length < 2) {
962
+ throw new ValidationError("search needs q, at least 2 characters", "q");
963
+ }
964
+ const r = await request("GET", "/v1/agents/search", {
965
+ query: {
966
+ q: options.q,
967
+ agent_id: options.agentId,
968
+ channel_id: options.channelId,
969
+ type: options.type,
970
+ include: list(options.include),
971
+ from: iso2(options.from),
972
+ to: iso2(options.to),
973
+ cursor: options.cursor,
974
+ limit: options.limit
975
+ }
976
+ });
977
+ return {
978
+ results: r.results ?? [],
979
+ nextCursor: r.next_cursor ?? null,
980
+ window: r.window
981
+ };
982
+ });
983
+ }
984
+ return {
985
+ insights: {
986
+ graph(filters = {}) {
987
+ return request("GET", "/v1/agents/insights/graph", {
988
+ query: {
989
+ ...rangeQuery(filters),
990
+ team_id: filters.teamId,
991
+ limit: filters.limit,
992
+ edge_limit: filters.edgeLimit
993
+ }
994
+ });
995
+ },
996
+ edge,
997
+ edgeAll(source, target, options = {}) {
998
+ const { max, ...rest } = options;
999
+ return paginate(async (cursor) => {
1000
+ const page = await edge(source, target, { ...rest, cursor });
1001
+ return { items: page.threads, nextCursor: page.nextCursor };
1002
+ }, max);
1003
+ },
1004
+ cost(options = {}) {
1005
+ return request("GET", "/v1/agents/insights/cost", {
1006
+ query: {
1007
+ ...rangeQuery(options),
1008
+ group_by: options.groupBy,
1009
+ team_id: options.teamId,
1010
+ limit: options.limit
1011
+ }
1012
+ });
1013
+ },
1014
+ metrics(filters = {}) {
1015
+ return request("GET", "/v1/agents/insights/metrics", {
1016
+ query: rangeQuery(filters)
1017
+ });
1018
+ }
1019
+ },
1020
+ search,
1021
+ searchAll(options) {
1022
+ const { max, ...rest } = options ?? {};
1023
+ return paginate(async (cursor) => {
1024
+ const page = await search({ ...rest, cursor });
1025
+ return { items: page.results, nextCursor: page.nextCursor };
1026
+ }, max);
1027
+ },
1028
+ exports: {
1029
+ download(kind, format = "csv", filters = {}) {
1030
+ return safely(async () => {
1031
+ need2(kind, "The export kind", "kind");
1032
+ const res = await requestText2("/v1/agents/export", {
1033
+ query: { kind, format, ...exportFilterFields(filters) }
1034
+ });
1035
+ return {
1036
+ body: res.body,
1037
+ rows: Number(res.headers.get("x-export-rows") ?? 0) || 0,
1038
+ truncated: res.headers.get("x-export-truncated") === "true",
1039
+ contentType: res.headers.get("content-type") ?? "",
1040
+ filename: filenameOf(res.headers.get("content-disposition"))
1041
+ };
1042
+ });
1043
+ },
1044
+ start(input) {
1045
+ return safely(async () => {
1046
+ need2(input?.kind, "The export kind", "kind");
1047
+ const r = await request("POST", "/v1/agents/export", {
1048
+ body: {
1049
+ kind: input.kind,
1050
+ format: input.format,
1051
+ filters: defined(exportFilterFields(input.filters))
1052
+ }
1053
+ });
1054
+ return { exportId: r.export_id, status: r.status, export: r.export };
1055
+ });
1056
+ },
1057
+ get(exportId) {
1058
+ return safely(async () => {
1059
+ need2(exportId, "The export id", "exportId");
1060
+ const r = await request(
1061
+ "GET",
1062
+ `/v1/agents/export/${enc2(exportId)}`
1063
+ );
1064
+ return r.export;
1065
+ });
1066
+ },
1067
+ async list(options = {}) {
1068
+ const r = await request(
1069
+ "GET",
1070
+ "/v1/agents/export",
1071
+ { query: { limit: options.limit } }
1072
+ );
1073
+ return r.exports ?? [];
1074
+ }
1075
+ },
1076
+ telemetry: {
1077
+ async getOtel() {
1078
+ return (await request("GET", "/v1/agents/telemetry/otel")).otel;
1079
+ },
1080
+ async setOtel(input) {
1081
+ const r = await request(
1082
+ "PUT",
1083
+ "/v1/agents/telemetry/otel",
1084
+ {
1085
+ body: {
1086
+ enabled: input?.enabled,
1087
+ endpoint_url: input?.endpointUrl,
1088
+ headers: input?.headers
1089
+ }
1090
+ }
1091
+ );
1092
+ return r.otel;
1093
+ },
1094
+ testOtel() {
1095
+ return request(
1096
+ "POST",
1097
+ "/v1/agents/telemetry/otel/test"
1098
+ );
1099
+ },
1100
+ async getWebhook() {
1101
+ return (await request(
1102
+ "GET",
1103
+ "/v1/agents/telemetry/webhook"
1104
+ )).webhook;
1105
+ },
1106
+ setWebhook(input) {
1107
+ return request(
1108
+ "PUT",
1109
+ "/v1/agents/telemetry/webhook",
1110
+ {
1111
+ body: {
1112
+ enabled: input?.enabled,
1113
+ url: input?.url,
1114
+ rotate_secret: input?.rotateSecret
1115
+ }
1116
+ }
1117
+ );
1118
+ },
1119
+ testWebhook() {
1120
+ return request(
1121
+ "POST",
1122
+ "/v1/agents/telemetry/webhook/test"
1123
+ );
1124
+ }
1125
+ }
1126
+ };
1127
+ }
1128
+
851
1129
  // src/agents.ts
852
1130
  function resolve(config) {
853
1131
  const env = readChannlizeEnv();
@@ -874,7 +1152,7 @@ function resolve(config) {
874
1152
  timeout: config.timeout ?? DEFAULT_TIMEOUT
875
1153
  };
876
1154
  }
877
- var enc2 = encodeURIComponent;
1155
+ var enc3 = encodeURIComponent;
878
1156
  function isFatal(error) {
879
1157
  return error instanceof ChannlizeError && [400, 401, 403, 404].includes(error.statusCode ?? 0);
880
1158
  }
@@ -907,6 +1185,17 @@ function createAgentsClient(config = {}) {
907
1185
  });
908
1186
  return res;
909
1187
  };
1188
+ const fetchText = (path, options = {}) => {
1189
+ const params = new URLSearchParams();
1190
+ for (const [k, v] of Object.entries(options.query ?? {})) {
1191
+ if (v !== void 0 && v !== "") params.set(k, String(v));
1192
+ }
1193
+ const qs = params.toString();
1194
+ return requestText(cfg, `${path}${qs ? `?${qs}` : ""}`, {
1195
+ method: "GET",
1196
+ headers: { Authorization: `Bearer ${cfg.apiKey}` }
1197
+ });
1198
+ };
910
1199
  function agentFor(agentId, what) {
911
1200
  const id = agentId ?? cfg.agentId;
912
1201
  if (!id) {
@@ -919,7 +1208,7 @@ function createAgentsClient(config = {}) {
919
1208
  }
920
1209
  async function inbox(agentId, options = {}) {
921
1210
  const id = agentFor(agentId, "inbox");
922
- const r = await request("GET", `/v1/agents/${enc2(id)}/inbox`, {
1211
+ const r = await request("GET", `/v1/agents/${enc3(id)}/inbox`, {
923
1212
  query: { cursor: options.cursor, limit: options.limit }
924
1213
  });
925
1214
  return {
@@ -939,7 +1228,7 @@ function createAgentsClient(config = {}) {
939
1228
  "ids"
940
1229
  );
941
1230
  }
942
- return request("POST", `/v1/agents/${enc2(id)}/inbox/ack`, {
1231
+ return request("POST", `/v1/agents/${enc3(id)}/inbox/ack`, {
943
1232
  body
944
1233
  });
945
1234
  }
@@ -994,6 +1283,7 @@ function createAgentsClient(config = {}) {
994
1283
  });
995
1284
  return {
996
1285
  ...createAgentsGovernance(request, cfg.agentId),
1286
+ ...createAgentsObservability(request, fetchText),
997
1287
  agentId: cfg.agentId,
998
1288
  send(input) {
999
1289
  if (!input || typeof input.channel !== "string" || !input.channel) {
@@ -1027,7 +1317,7 @@ function createAgentsClient(config = {}) {
1027
1317
  return r.agents ?? [];
1028
1318
  },
1029
1319
  get(agentId) {
1030
- return request("GET", `/v1/agents/${enc2(agentId)}`);
1320
+ return request("GET", `/v1/agents/${enc3(agentId)}`);
1031
1321
  },
1032
1322
  channels: {
1033
1323
  async list(options = {}) {
@@ -1039,20 +1329,20 @@ function createAgentsClient(config = {}) {
1039
1329
  return r.channels ?? [];
1040
1330
  },
1041
1331
  get(channelId, options) {
1042
- return request("GET", `/v1/agents/channels/${enc2(channelId)}`, {
1332
+ return request("GET", `/v1/agents/channels/${enc3(channelId)}`, {
1043
1333
  query: channelRead(options)
1044
1334
  });
1045
1335
  },
1046
1336
  async members(channelId, options) {
1047
1337
  const r = await request(
1048
1338
  "GET",
1049
- `/v1/agents/channels/${enc2(channelId)}/members`,
1339
+ `/v1/agents/channels/${enc3(channelId)}/members`,
1050
1340
  { query: channelRead(options) }
1051
1341
  );
1052
1342
  return r.members ?? [];
1053
1343
  },
1054
1344
  async messages(channelId, options = {}) {
1055
- const r = await request("GET", `/v1/agents/channels/${enc2(channelId)}/messages`, {
1345
+ const r = await request("GET", `/v1/agents/channels/${enc3(channelId)}/messages`, {
1056
1346
  query: {
1057
1347
  ...channelRead(options),
1058
1348
  cursor: options.cursor,
@@ -1088,7 +1378,7 @@ async function getSubtle() {
1088
1378
  } catch {
1089
1379
  }
1090
1380
  throw new ChannlizeError(
1091
- "verifyAgentWebhook needs the Web Crypto API (globalThis.crypto.subtle, or node:crypto webcrypto on Node.js 18)"
1381
+ "Webhook signature checks need the Web Crypto API (globalThis.crypto.subtle, or node:crypto webcrypto on Node.js 18)"
1092
1382
  );
1093
1383
  }
1094
1384
  function hexToBytes(hex) {
@@ -1110,6 +1400,9 @@ function toBytes(body) {
1110
1400
  async function verifyAgentWebhook(rawBody, signatureHeader, secret) {
1111
1401
  if (!secret)
1112
1402
  throw new ValidationError("The agent secret is required", "secret");
1403
+ return verifyHmacSha256(rawBody, signatureHeader, secret);
1404
+ }
1405
+ async function verifyHmacSha256(rawBody, signatureHeader, secret) {
1113
1406
  const header = Array.isArray(signatureHeader) ? signatureHeader[0] : signatureHeader;
1114
1407
  const m = /^sha256=([0-9a-f]+)$/i.exec(header?.trim() ?? "");
1115
1408
  const expected = m ? hexToBytes(m[1]) : null;
@@ -1142,6 +1435,26 @@ async function parseAgentWebhookEvent(rawBody, signatureHeader, secret) {
1142
1435
  const text = typeof rawBody === "string" ? rawBody : new TextDecoder().decode(rawBody);
1143
1436
  return JSON.parse(text);
1144
1437
  }
1438
+ async function verifyTelemetryWebhook(rawBody, signatureHeader, secret) {
1439
+ if (!secret) {
1440
+ throw new ValidationError(
1441
+ "The telemetry webhook secret is required",
1442
+ "secret"
1443
+ );
1444
+ }
1445
+ return verifyHmacSha256(rawBody, signatureHeader, secret);
1446
+ }
1447
+ async function parseTelemetryWebhook(rawBody, signatureHeader, secret) {
1448
+ if (!await verifyTelemetryWebhook(rawBody, signatureHeader, secret)) {
1449
+ throw new ChannlizeError(
1450
+ "Invalid telemetry webhook signature",
1451
+ 401,
1452
+ "INVALID_SIGNATURE"
1453
+ );
1454
+ }
1455
+ const text = typeof rawBody === "string" ? rawBody : new TextDecoder().decode(rawBody);
1456
+ return JSON.parse(text);
1457
+ }
1145
1458
 
1146
1459
  // src/client.ts
1147
1460
  var ChannlizeClient = class {
@@ -1335,9 +1648,9 @@ var ChannlizeClient = class {
1335
1648
  * plan's limit for the billing period) resolves with `success: false`
1336
1649
  * and `dropped` instead of throwing, with one console warning per client.
1337
1650
  */
1338
- async nonFatalTracking(send) {
1651
+ async nonFatalTracking(send2) {
1339
1652
  try {
1340
- return await send();
1653
+ return await send2();
1341
1654
  } catch (error) {
1342
1655
  if (!(error instanceof RateLimitError)) throw error;
1343
1656
  const dropped = error.code === "PLAN_LIMIT_REACHED" ? "plan_limit" : "rate_limit";
@@ -1419,8 +1732,28 @@ var AGENT_TASK_STATUSES = [
1419
1732
  "canceled",
1420
1733
  "rejected"
1421
1734
  ];
1735
+
1736
+ // src/agents-observability-types.ts
1737
+ var COST_GROUPINGS = [
1738
+ "agent",
1739
+ "channel",
1740
+ "team",
1741
+ "task",
1742
+ "day"
1743
+ ];
1744
+ var AGENT_EXPORT_KINDS = [
1745
+ "messages",
1746
+ "tasks",
1747
+ "questions",
1748
+ "audit"
1749
+ ];
1750
+ var AGENT_EXPORT_FORMATS = ["csv", "json"];
1751
+ var AGENT_EXPORT_SYNC_MAX_ROWS = 1e4;
1422
1752
  export {
1423
1753
  AGENT_DELIVERY_MODES,
1754
+ AGENT_EXPORT_FORMATS,
1755
+ AGENT_EXPORT_KINDS,
1756
+ AGENT_EXPORT_SYNC_MAX_ROWS,
1424
1757
  AGENT_MESSAGE_TYPES,
1425
1758
  AGENT_POLICY_CODES,
1426
1759
  AGENT_STATUSES,
@@ -1430,6 +1763,7 @@ export {
1430
1763
  AgentTaskError,
1431
1764
  AgentThreadPausedError,
1432
1765
  AuthenticationError,
1766
+ COST_GROUPINGS,
1433
1767
  ChannlizeClient,
1434
1768
  ChannlizeError,
1435
1769
  DEFAULT_BASE_URL,
@@ -1461,8 +1795,10 @@ export {
1461
1795
  parseAgentWebhook,
1462
1796
  parseAgentWebhookEvent,
1463
1797
  parseApiError,
1798
+ parseTelemetryWebhook,
1464
1799
  readChannlizeEnv,
1465
1800
  retryAfter,
1466
- verifyAgentWebhook
1801
+ verifyAgentWebhook,
1802
+ verifyTelemetryWebhook
1467
1803
  };
1468
1804
  //# sourceMappingURL=index.mjs.map