@channlize/core 0.2.1 → 0.5.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
@@ -30,19 +30,98 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
30
30
  // src/index.ts
31
31
  var index_exports = {};
32
32
  __export(index_exports, {
33
+ AGENT_DELIVERY_MODES: () => AGENT_DELIVERY_MODES,
34
+ AGENT_MESSAGE_TYPES: () => AGENT_MESSAGE_TYPES,
35
+ AGENT_POLICY_CODES: () => AGENT_POLICY_CODES,
36
+ AGENT_STATUSES: () => AGENT_STATUSES,
37
+ AGENT_TASK_ERROR_CODES: () => AGENT_TASK_ERROR_CODES,
38
+ AGENT_TASK_STATUSES: () => AGENT_TASK_STATUSES,
39
+ AgentPolicyError: () => AgentPolicyError,
40
+ AgentTaskError: () => AgentTaskError,
41
+ AgentThreadPausedError: () => AgentThreadPausedError,
33
42
  AuthenticationError: () => AuthenticationError,
34
43
  ChannlizeClient: () => ChannlizeClient,
35
44
  ChannlizeError: () => ChannlizeError,
45
+ DEFAULT_BASE_URL: () => DEFAULT_BASE_URL,
46
+ DEFAULT_TIMEOUT: () => DEFAULT_TIMEOUT,
47
+ EVENT_KEY_PATTERN: () => EVENT_KEY_PATTERN,
48
+ EVENT_SEVERITIES: () => EVENT_SEVERITIES,
49
+ HumanTakeoverError: () => HumanTakeoverError,
50
+ POLICY_SCOPES: () => POLICY_SCOPES,
51
+ QUESTION_KINDS: () => QUESTION_KINDS,
52
+ QUESTION_STATUSES: () => QUESTION_STATUSES,
53
+ QUESTION_URGENCIES: () => QUESTION_URGENCIES,
54
+ QUESTION_WAIT_MAX_SECONDS: () => QUESTION_WAIT_MAX_SECONDS,
55
+ QuestionClosedError: () => QuestionClosedError,
36
56
  RateLimitError: () => RateLimitError,
57
+ SENDABLE_AGENT_MESSAGE_TYPES: () => SENDABLE_AGENT_MESSAGE_TYPES,
58
+ TASK_BOARD_STATUSES: () => TASK_BOARD_STATUSES,
37
59
  ValidationError: () => ValidationError,
60
+ createAgentsClient: () => createAgentsClient,
38
61
  createNotifier: () => createNotifier,
39
- default: () => ChannlizeClient
62
+ default: () => ChannlizeClient,
63
+ isAgentMessageWebhook: () => isAgentMessageWebhook,
64
+ isQuestionEscalatedWebhook: () => isQuestionEscalatedWebhook,
65
+ isQuestionOutcomeMessage: () => isQuestionOutcomeMessage,
66
+ isQuestionOutcomeWebhook: () => isQuestionOutcomeWebhook,
67
+ isRetryable: () => isRetryable,
68
+ isTaskHandoffMessage: () => isTaskHandoffMessage,
69
+ isValidEventKey: () => isValidEventKey,
70
+ keyKind: () => keyKind,
71
+ parseAgentWebhook: () => parseAgentWebhook,
72
+ parseAgentWebhookEvent: () => parseAgentWebhookEvent,
73
+ parseApiError: () => parseApiError,
74
+ readChannlizeEnv: () => readChannlizeEnv,
75
+ retryAfter: () => retryAfter,
76
+ verifyAgentWebhook: () => verifyAgentWebhook
40
77
  });
41
78
  module.exports = __toCommonJS(index_exports);
42
79
 
43
80
  // src/http.ts
44
81
  var import_cross_fetch = __toESM(require("cross-fetch"));
45
82
 
83
+ // src/shared.ts
84
+ var DEFAULT_BASE_URL = "https://api.channlize.com";
85
+ var DEFAULT_TIMEOUT = 1e4;
86
+ var EVENT_SEVERITIES = ["info", "warning", "error"];
87
+ var EVENT_KEY_PATTERN = /^[a-z0-9][a-z0-9._-]*$/;
88
+ function isValidEventKey(event) {
89
+ return typeof event === "string" && event.length <= 100 && EVENT_KEY_PATTERN.test(event);
90
+ }
91
+ function keyKind(key) {
92
+ return key.startsWith("wk_") ? "widget" : "server";
93
+ }
94
+ function clean(value) {
95
+ const v = value?.trim();
96
+ return v ? v : void 0;
97
+ }
98
+ function readChannlizeEnv(env = typeof process !== "undefined" ? process.env : void 0) {
99
+ return {
100
+ serverApiKey: clean(env?.CHANNLIZE_SERVER_API_KEY),
101
+ widgetKey: clean(env?.CHANNLIZE_WIDGET_KEY),
102
+ apiKey: clean(env?.CHANNLIZE_API_KEY),
103
+ projectId: clean(env?.CHANNLIZE_PROJECT_ID),
104
+ baseUrl: clean(env?.CHANNLIZE_BASE_URL),
105
+ timeout: clean(env?.CHANNLIZE_TIMEOUT),
106
+ agentId: clean(env?.CHANNLIZE_AGENT_ID)
107
+ };
108
+ }
109
+ function parseApiError(status, body, statusText = "") {
110
+ const data = body && typeof body === "object" ? body : {};
111
+ const raw = data.message;
112
+ const joined = Array.isArray(raw) ? raw.filter((m) => typeof m === "string").join(", ") : typeof raw === "string" ? raw : "";
113
+ const error = typeof data.error === "string" ? data.error : void 0;
114
+ const code = typeof data.code === "string" ? data.code : error;
115
+ const message = joined || (error && /\s/.test(error) ? error : "") || `HTTP ${status}: ${statusText}`.replace(/: $/, "");
116
+ return {
117
+ status,
118
+ message,
119
+ code,
120
+ requestId: typeof data.requestId === "string" ? data.requestId : void 0,
121
+ body: data
122
+ };
123
+ }
124
+
46
125
  // src/types.ts
47
126
  var ChannlizeError = class extends Error {
48
127
  constructor(message, statusCode, code) {
@@ -53,6 +132,19 @@ var ChannlizeError = class extends Error {
53
132
  }
54
133
  statusCode;
55
134
  code;
135
+ /** The API's `requestId` for a failed response, to quote to support. */
136
+ requestId;
137
+ /**
138
+ * The API's JSON error body, e.g. `limit`, `used`, `resetAt` and
139
+ * `upgradeUrl` on a plan-limit 429. Undefined for network errors.
140
+ */
141
+ details;
142
+ /**
143
+ * ISO time the call may succeed again: the body's `retryAt` (agent
144
+ * policies, human takeovers), else derived from a `Retry-After` header.
145
+ * See `retryAfter(error)`.
146
+ */
147
+ retryAt;
56
148
  };
57
149
  var ValidationError = class extends ChannlizeError {
58
150
  constructor(message, field) {
@@ -69,17 +161,112 @@ var AuthenticationError = class extends ChannlizeError {
69
161
  }
70
162
  };
71
163
  var RateLimitError = class extends ChannlizeError {
72
- constructor(message = "Rate limit exceeded") {
73
- super(message, 429, "RATE_LIMIT_ERROR");
164
+ constructor(message = "Rate limit exceeded", code = "RATE_LIMIT_ERROR") {
165
+ super(message, 429, code);
74
166
  this.name = "RateLimitError";
75
167
  }
76
168
  };
77
169
 
170
+ // src/agents-errors.ts
171
+ var AGENT_POLICY_CODES = [
172
+ "POLICY_CHANNEL_NOT_ALLOWED",
173
+ "POLICY_DM_NOT_ALLOWED",
174
+ "POLICY_ASK_HUMAN_NOT_ALLOWED",
175
+ "POLICY_URGENCY_NOT_ALLOWED",
176
+ "POLICY_RATE_LIMITED",
177
+ "POLICY_BUDGET_EXHAUSTED",
178
+ "POLICY_OUTSIDE_WORKING_HOURS",
179
+ "POLICY_MAX_DEPTH"
180
+ ];
181
+ var AGENT_TASK_ERROR_CODES = [
182
+ "TASK_CLOSED",
183
+ "ALREADY_ASSIGNED",
184
+ "AGENT_NOT_IN_CHANNEL",
185
+ "NOT_TASK_PARTICIPANT"
186
+ ];
187
+ var AgentPolicyError = class extends ChannlizeError {
188
+ constructor(message, statusCode, code, retryAt) {
189
+ super(message, statusCode, code);
190
+ this.name = "AgentPolicyError";
191
+ this.retryAt = retryAt;
192
+ }
193
+ };
194
+ var HumanTakeoverError = class extends ChannlizeError {
195
+ constructor(message, retryAt) {
196
+ super(message, 409, "HUMAN_TAKEOVER");
197
+ this.name = "HumanTakeoverError";
198
+ this.retryAt = retryAt;
199
+ }
200
+ };
201
+ var AgentThreadPausedError = class extends RateLimitError {
202
+ constructor(message, reason = "thread_cap") {
203
+ super(message, "AGENT_THREAD_PAUSED");
204
+ this.reason = reason;
205
+ this.name = "AgentThreadPausedError";
206
+ }
207
+ reason;
208
+ };
209
+ var AgentTaskError = class extends ChannlizeError {
210
+ constructor(message, statusCode, code) {
211
+ super(message, statusCode, code);
212
+ this.name = "AgentTaskError";
213
+ }
214
+ };
215
+ var QuestionClosedError = class extends ChannlizeError {
216
+ constructor(message) {
217
+ super(message, 409, "QUESTION_CLOSED");
218
+ this.name = "QuestionClosedError";
219
+ }
220
+ };
221
+ function agentErrorFor(status, message, code, body, retryAt) {
222
+ if (!code) return void 0;
223
+ if (code.startsWith("POLICY_")) {
224
+ return new AgentPolicyError(message, status, code, retryAt);
225
+ }
226
+ switch (code) {
227
+ case "HUMAN_TAKEOVER":
228
+ return new HumanTakeoverError(message, retryAt);
229
+ case "AGENT_THREAD_PAUSED":
230
+ return new AgentThreadPausedError(
231
+ message,
232
+ typeof body.reason === "string" ? body.reason : void 0
233
+ );
234
+ case "AGENT_RATE_LIMITED":
235
+ return new RateLimitError(message, code);
236
+ case "QUESTION_CLOSED":
237
+ return new QuestionClosedError(message);
238
+ default:
239
+ return AGENT_TASK_ERROR_CODES.includes(code) ? new AgentTaskError(message, status, code) : void 0;
240
+ }
241
+ }
242
+ function isRetryable(error) {
243
+ if (!(error instanceof ChannlizeError)) return false;
244
+ const { statusCode: status, code } = error;
245
+ if (code === "NETWORK_ERROR" || code === "TIMEOUT") return true;
246
+ if (status === void 0) return false;
247
+ if (status >= 500) return true;
248
+ if (error instanceof AgentPolicyError || error instanceof HumanTakeoverError) {
249
+ return Boolean(error.retryAt);
250
+ }
251
+ if (status === 429) return code !== "PLAN_LIMIT_REACHED";
252
+ return false;
253
+ }
254
+ function retryAfter(error, now = Date.now()) {
255
+ if (!isRetryable(error)) return void 0;
256
+ const at = error.retryAt;
257
+ const ms = at ? Date.parse(at) : NaN;
258
+ return Number.isFinite(ms) ? Math.max(0, ms - now) : void 0;
259
+ }
260
+
78
261
  // src/http.ts
79
262
  async function requestJson(http, endpoint, options = {}) {
80
263
  const url = `${http.baseUrl}${endpoint}`;
81
264
  const controller = new AbortController();
82
265
  const timeoutId = setTimeout(() => controller.abort(), http.timeout);
266
+ const external = options.signal ?? void 0;
267
+ const onAbort = () => controller.abort();
268
+ if (external?.aborted) controller.abort();
269
+ else external?.addEventListener("abort", onAbort, { once: true });
83
270
  try {
84
271
  const mergedHeaders = {
85
272
  "Content-Type": "application/json",
@@ -104,52 +291,93 @@ async function requestJson(http, endpoint, options = {}) {
104
291
  if (error instanceof ChannlizeError) {
105
292
  throw error;
106
293
  }
294
+ if (external?.aborted) {
295
+ throw new ChannlizeError("Request aborted", void 0, "ABORTED");
296
+ }
107
297
  if (error instanceof Error && error.name === "AbortError") {
108
298
  throw new ChannlizeError("Request timeout", 408, "TIMEOUT");
109
299
  }
110
300
  throw new ChannlizeError(
111
- `Network error: ${error instanceof Error ? error.message : "Unknown error"}`
301
+ `Network error: ${error instanceof Error ? error.message : "Unknown error"}`,
302
+ void 0,
303
+ "NETWORK_ERROR"
112
304
  );
305
+ } finally {
306
+ external?.removeEventListener("abort", onAbort);
113
307
  }
114
308
  }
309
+ function retryAfterHeader(value) {
310
+ if (!value) return void 0;
311
+ const seconds = Number(value);
312
+ const ms = Number.isFinite(seconds) ? Date.now() + seconds * 1e3 : Date.parse(value);
313
+ return Number.isFinite(ms) ? new Date(ms).toISOString() : void 0;
314
+ }
115
315
  async function handleErrorResponse(response) {
116
- const contentType = response.headers.get("content-type");
117
- let errorData = {};
118
- if (contentType?.includes("application/json")) {
316
+ let body = {};
317
+ if (response.headers.get("content-type")?.includes("application/json")) {
119
318
  try {
120
- errorData = await response.json();
319
+ body = await response.json();
121
320
  } catch {
122
321
  }
123
322
  }
124
- const rawMessage = errorData.message;
125
- const message = (Array.isArray(rawMessage) ? rawMessage.join(", ") : rawMessage) || `HTTP ${response.status}: ${response.statusText}`;
126
- const code = errorData.code ?? errorData.error;
127
- switch (response.status) {
323
+ throw errorFromApi(
324
+ parseApiError(response.status, body, response.statusText),
325
+ retryAfterHeader(response.headers.get("retry-after"))
326
+ );
327
+ }
328
+ function errorFromApi(info, retryAfter2) {
329
+ const { status, message, code } = info;
330
+ const retryAt = typeof info.body.retryAt === "string" ? info.body.retryAt : retryAfter2;
331
+ let error = agentErrorFor(
332
+ status,
333
+ message,
334
+ code,
335
+ info.body,
336
+ retryAt
337
+ );
338
+ if (error) {
339
+ error.requestId = info.requestId;
340
+ error.details = info.body;
341
+ error.retryAt ??= retryAt;
342
+ return error;
343
+ }
344
+ switch (status) {
128
345
  case 401:
129
- throw new AuthenticationError(message);
346
+ error = new AuthenticationError(message);
347
+ break;
130
348
  case 400:
131
- throw new ValidationError(message, errorData.field);
349
+ error = new ValidationError(
350
+ message,
351
+ typeof info.body.field === "string" ? info.body.field : void 0
352
+ );
353
+ break;
132
354
  case 429:
133
- throw new RateLimitError(message);
355
+ error = new RateLimitError(
356
+ message,
357
+ code === "PLAN_LIMIT_REACHED" ? code : void 0
358
+ );
359
+ break;
134
360
  default:
135
- throw new ChannlizeError(message, response.status, code);
361
+ error = new ChannlizeError(message, status, code);
136
362
  }
363
+ error.requestId = info.requestId;
364
+ error.details = info.body;
365
+ error.retryAt = retryAt;
366
+ return error;
137
367
  }
138
368
 
139
369
  // src/notify.ts
140
- var EVENT_KEY = /^[a-z0-9][a-z0-9._-]*$/;
141
- var DEFAULT_BASE_URL = "https://api.channlize.com";
142
370
  async function sendNotify(http, auth, event, data, options = {}) {
143
371
  if (!auth.projectId) {
144
372
  throw new ValidationError("projectId is required for notify", "projectId");
145
373
  }
146
- if (auth.serverApiKey.startsWith("wk_")) {
374
+ if (keyKind(auth.serverApiKey) === "widget") {
147
375
  throw new ValidationError(
148
376
  "notify needs a server API key (ch_live_\u2026, write scope), not the public widget key",
149
377
  "serverApiKey"
150
378
  );
151
379
  }
152
- if (typeof event !== "string" || event.length > 100 || !EVENT_KEY.test(event)) {
380
+ if (!isValidEventKey(event)) {
153
381
  throw new ValidationError(
154
382
  `Invalid event key "${event}": use lowercase letters, digits, dots, underscores or dashes (e.g. user.subscribed)`,
155
383
  "event"
@@ -173,15 +401,11 @@ async function sendNotify(http, auth, event, data, options = {}) {
173
401
  });
174
402
  return res;
175
403
  }
176
- function readEnv(name) {
177
- const env = typeof process !== "undefined" && process.env ? process.env : void 0;
178
- const value = env?.[name];
179
- return value && value.trim() ? value.trim() : void 0;
180
- }
181
404
  function createNotifier(config = {}) {
182
405
  return (event, data, options) => {
183
- const serverApiKey = config.serverApiKey ?? readEnv("CHANNLIZE_SERVER_API_KEY") ?? readEnv("CHANNLIZE_API_KEY");
184
- const projectId = config.projectId ?? readEnv("CHANNLIZE_PROJECT_ID");
406
+ const env = readChannlizeEnv();
407
+ const serverApiKey = config.serverApiKey ?? env.serverApiKey ?? env.apiKey;
408
+ const projectId = config.projectId ?? env.projectId;
185
409
  if (!serverApiKey) {
186
410
  return Promise.reject(
187
411
  new ValidationError(
@@ -198,9 +422,12 @@ function createNotifier(config = {}) {
198
422
  )
199
423
  );
200
424
  }
201
- const baseUrl = config.baseUrl ?? readEnv("CHANNLIZE_BASE_URL") ?? DEFAULT_BASE_URL;
425
+ const baseUrl = config.baseUrl ?? env.baseUrl ?? DEFAULT_BASE_URL;
202
426
  return sendNotify(
203
- { baseUrl: baseUrl.replace(/\/$/, ""), timeout: config.timeout ?? 1e4 },
427
+ {
428
+ baseUrl: baseUrl.replace(/\/$/, ""),
429
+ timeout: config.timeout ?? DEFAULT_TIMEOUT
430
+ },
204
431
  { serverApiKey, projectId },
205
432
  event,
206
433
  data,
@@ -209,6 +436,792 @@ function createNotifier(config = {}) {
209
436
  };
210
437
  }
211
438
 
439
+ // src/agents-governance-types.ts
440
+ var QUESTION_KINDS = ["question", "approval"];
441
+ var QUESTION_URGENCIES = ["low", "normal", "high", "urgent"];
442
+ var QUESTION_STATUSES = [
443
+ "open",
444
+ "answered",
445
+ "expired_default",
446
+ "expired",
447
+ "canceled"
448
+ ];
449
+ var QUESTION_WAIT_MAX_SECONDS = 25;
450
+ var TASK_BOARD_STATUSES = [
451
+ "open",
452
+ "in_progress",
453
+ "blocked",
454
+ "done",
455
+ "failed"
456
+ ];
457
+ var POLICY_SCOPES = ["org", "team", "agent", "channel"];
458
+
459
+ // src/agents-governance.ts
460
+ var enc = encodeURIComponent;
461
+ var iso = (v) => v instanceof Date ? v.toISOString() : v;
462
+ function sleep(ms, signal) {
463
+ return new Promise((done) => {
464
+ if (signal?.aborted) return done();
465
+ const timer = setTimeout(finish, ms);
466
+ function finish() {
467
+ clearTimeout(timer);
468
+ signal?.removeEventListener("abort", finish);
469
+ done();
470
+ }
471
+ signal?.addEventListener("abort", finish, { once: true });
472
+ });
473
+ }
474
+ function aborted() {
475
+ return new ChannlizeError("Request aborted", void 0, "ABORTED");
476
+ }
477
+ function need(value, what, field) {
478
+ if (typeof value !== "string" || !value) {
479
+ throw new ValidationError(`${what} is required`, field);
480
+ }
481
+ }
482
+ function createAgentsGovernance(request, defaultAgentId) {
483
+ const agentOf = (agentId) => agentId ?? defaultAgentId;
484
+ const safely = (fn) => {
485
+ try {
486
+ return fn();
487
+ } catch (error) {
488
+ return Promise.reject(error);
489
+ }
490
+ };
491
+ function ask(input) {
492
+ return safely(async () => {
493
+ if (!input || typeof input.question !== "string" || !input.question) {
494
+ throw new ValidationError("ask needs a question", "question");
495
+ }
496
+ const r = await request(
497
+ "POST",
498
+ "/v1/agents/questions",
499
+ {
500
+ body: {
501
+ agent_id: agentOf(input.agentId),
502
+ kind: input.kind,
503
+ question: input.question,
504
+ options: input.options,
505
+ free_text: input.freeText,
506
+ urgency: input.urgency,
507
+ deadline: iso(input.deadline),
508
+ default_option: input.defaultOption,
509
+ channel_id: input.channelId,
510
+ thread_id: input.threadId,
511
+ context: input.context,
512
+ amount: input.amount,
513
+ currency: input.currency,
514
+ resource: input.resource,
515
+ required_roles: input.requiredRoles
516
+ }
517
+ }
518
+ );
519
+ return r.question;
520
+ });
521
+ }
522
+ function wait(questionId, options = {}) {
523
+ return safely(async () => {
524
+ need(questionId, "The question id", "questionId");
525
+ const timeoutSec = Math.min(
526
+ QUESTION_WAIT_MAX_SECONDS,
527
+ Math.max(0, options.timeoutSec ?? QUESTION_WAIT_MAX_SECONDS)
528
+ );
529
+ const r = await request(
530
+ "GET",
531
+ `/v1/agents/questions/${enc(questionId)}/wait`,
532
+ {
533
+ query: { agent_id: agentOf(options.agentId), timeout: timeoutSec },
534
+ // The server holds the request for up to timeoutSec.
535
+ timeout: (timeoutSec + 10) * 1e3,
536
+ signal: options.signal
537
+ }
538
+ );
539
+ return {
540
+ question: r.question,
541
+ timedOut: r.timed_out ?? r.question?.status === "open"
542
+ };
543
+ });
544
+ }
545
+ async function askAndWait(input, options = {}) {
546
+ const { signal } = options;
547
+ const deadline = Date.now() + Math.max(0, options.timeoutMs ?? 6e5);
548
+ if (signal?.aborted) throw aborted();
549
+ let question = await ask(input);
550
+ let failures = 0;
551
+ while (question.status === "open") {
552
+ if (signal?.aborted) throw aborted();
553
+ const left = deadline - Date.now();
554
+ if (left < 1e3) break;
555
+ try {
556
+ const r = await wait(question.id, {
557
+ agentId: input.agentId,
558
+ timeoutSec: Math.min(
559
+ QUESTION_WAIT_MAX_SECONDS,
560
+ Math.floor(left / 1e3)
561
+ ),
562
+ signal
563
+ });
564
+ question = r.question;
565
+ failures = 0;
566
+ } catch (error) {
567
+ if (signal?.aborted) throw aborted();
568
+ if (!isRetryable(error)) throw error;
569
+ failures += 1;
570
+ const backoff = retryAfter(error) ?? Math.min(3e4, 1e3 * 2 ** (failures - 1));
571
+ const remaining = deadline - Date.now();
572
+ if (backoff >= remaining) break;
573
+ await sleep(backoff, signal);
574
+ }
575
+ }
576
+ if (signal?.aborted && question.status === "open") throw aborted();
577
+ return {
578
+ question,
579
+ answer: question.answer ?? null,
580
+ timedOut: question.status === "open"
581
+ };
582
+ }
583
+ const questionPath = (id) => {
584
+ need(id, "The question id", "questionId");
585
+ return `/v1/agents/questions/${enc(id)}`;
586
+ };
587
+ const taskPath = (id) => {
588
+ need(id, "The task id", "taskId");
589
+ return `/v1/agents/tasks/${enc(id)}`;
590
+ };
591
+ const teamPath = (id) => {
592
+ need(id, "The team id", "teamId");
593
+ return `/v1/agents/teams/${enc(id)}`;
594
+ };
595
+ const agentPath = (id) => {
596
+ need(id, "The agent id", "agentId");
597
+ return enc(id);
598
+ };
599
+ return {
600
+ ask,
601
+ askAndWait,
602
+ questions: {
603
+ get: (id, options = {}) => safely(async () => {
604
+ const r = await request("GET", questionPath(id), {
605
+ query: { agent_id: agentOf(options.agentId) }
606
+ });
607
+ return { question: r.question, escalations: r.escalations ?? [] };
608
+ }),
609
+ list: (options = {}) => safely(async () => {
610
+ const r = await request("GET", "/v1/agents/questions", {
611
+ query: {
612
+ agent_id: agentOf(options.agentId),
613
+ status: options.status,
614
+ kind: options.kind,
615
+ urgency: options.urgency,
616
+ channel_id: options.channelId,
617
+ cursor: options.cursor,
618
+ limit: options.limit
619
+ }
620
+ });
621
+ return {
622
+ questions: r.questions ?? [],
623
+ nextCursor: r.next_cursor ?? null
624
+ };
625
+ }),
626
+ wait,
627
+ answer: (id, input) => safely(async () => {
628
+ if (!input?.optionId && !input?.text) {
629
+ throw new ValidationError(
630
+ "answer needs optionId or text",
631
+ "optionId"
632
+ );
633
+ }
634
+ const r = await request(
635
+ "POST",
636
+ `${questionPath(id)}/answer`,
637
+ {
638
+ body: {
639
+ agent_id: agentOf(input.agentId),
640
+ option_id: input.optionId,
641
+ text: input.text,
642
+ comment: input.comment
643
+ }
644
+ }
645
+ );
646
+ return r.question;
647
+ }),
648
+ cancel: (id, options = {}) => safely(async () => {
649
+ const r = await request(
650
+ "DELETE",
651
+ questionPath(id),
652
+ { query: { agent_id: agentOf(options.agentId) } }
653
+ );
654
+ return r.question;
655
+ })
656
+ },
657
+ tasks: {
658
+ create: (input) => safely(async () => {
659
+ need(input?.channelId, "channelId", "channelId");
660
+ const r = await request(
661
+ "POST",
662
+ "/v1/agents/tasks",
663
+ {
664
+ body: {
665
+ agent_id: agentOf(input.agentId),
666
+ channel_id: input.channelId,
667
+ text: input.text,
668
+ assignee_agent_id: input.assigneeAgentId,
669
+ due_at: iso(input.dueAt),
670
+ thread_id: input.threadId,
671
+ mentions: input.mentions,
672
+ data: input.data
673
+ }
674
+ }
675
+ );
676
+ return { task: r.task, deliveries: r.deliveries ?? [] };
677
+ }),
678
+ list: (options = {}) => safely(async () => {
679
+ const r = await request(
680
+ "GET",
681
+ "/v1/agents/tasks",
682
+ {
683
+ query: {
684
+ agent_id: agentOf(options.agentId),
685
+ channel_id: options.channelId,
686
+ assignee_agent_id: options.assigneeAgentId,
687
+ status: options.status,
688
+ limit: options.limit
689
+ }
690
+ }
691
+ );
692
+ return r.tasks ?? [];
693
+ }),
694
+ board: (channelId, options = {}) => safely(() => {
695
+ need(channelId, "channelId", "channelId");
696
+ return request("GET", "/v1/agents/tasks/board", {
697
+ query: {
698
+ channel_id: channelId,
699
+ assignee_agent_id: options.assigneeAgentId,
700
+ agent_id: agentOf(options.agentId)
701
+ }
702
+ });
703
+ }),
704
+ get: (taskId, options = {}) => safely(async () => {
705
+ const r = await request("GET", taskPath(taskId), {
706
+ query: { agent_id: agentOf(options.agentId) }
707
+ });
708
+ return { task: r.task, events: r.events ?? [] };
709
+ }),
710
+ update: (taskId, input) => safely(() => {
711
+ if (!input || input.status === void 0 && input.dueAt === void 0) {
712
+ throw new ValidationError("update needs status or dueAt", "status");
713
+ }
714
+ return request("PATCH", taskPath(taskId), {
715
+ body: {
716
+ agent_id: agentOf(input.agentId),
717
+ status: input.status,
718
+ due_at: input.dueAt === null ? null : iso(input.dueAt),
719
+ note: input.note
720
+ }
721
+ });
722
+ }),
723
+ handoff: (taskId, input) => safely(() => {
724
+ need(input?.toAgentId, "toAgentId", "toAgentId");
725
+ return request(
726
+ "POST",
727
+ `${taskPath(taskId)}/handoff`,
728
+ {
729
+ body: {
730
+ agent_id: agentOf(input.agentId),
731
+ to_agent_id: input.toAgentId,
732
+ reason: input.reason
733
+ }
734
+ }
735
+ );
736
+ })
737
+ },
738
+ teams: {
739
+ list: () => safely(async () => {
740
+ const r = await request(
741
+ "GET",
742
+ "/v1/agents/teams"
743
+ );
744
+ return r.teams ?? [];
745
+ }),
746
+ get: (teamId) => safely(() => request("GET", teamPath(teamId))),
747
+ create: (input) => safely(async () => {
748
+ const r = await request(
749
+ "POST",
750
+ "/v1/agents/teams",
751
+ {
752
+ body: {
753
+ name: input.name,
754
+ slug: input.slug,
755
+ description: input.description,
756
+ agent_ids: input.agentIds
757
+ }
758
+ }
759
+ );
760
+ return r.team;
761
+ }),
762
+ update: (teamId, input) => safely(async () => {
763
+ const r = await request(
764
+ "PATCH",
765
+ teamPath(teamId),
766
+ { body: input }
767
+ );
768
+ return r.team;
769
+ }),
770
+ delete: (teamId) => safely(async () => {
771
+ const r = await request(
772
+ "DELETE",
773
+ teamPath(teamId)
774
+ );
775
+ return { memberships_removed: r.memberships_removed ?? 0 };
776
+ }),
777
+ addMember: (teamId, agentId) => safely(() => {
778
+ need(agentId, "agentId", "agentId");
779
+ return request(
780
+ "POST",
781
+ `${teamPath(teamId)}/members`,
782
+ { body: { agent_id: agentId } }
783
+ );
784
+ }),
785
+ removeMember: (teamId, agentId) => safely(async () => {
786
+ const r = await request(
787
+ "DELETE",
788
+ `${teamPath(teamId)}/members/${agentPath(agentId)}`
789
+ );
790
+ return { memberships_removed: r.memberships_removed ?? 0 };
791
+ }),
792
+ grantChannel: (teamId, channelId, role) => safely(() => {
793
+ need(channelId, "channelId", "channelId");
794
+ return request("POST", `${teamPath(teamId)}/channels`, {
795
+ body: { channel_id: channelId, role }
796
+ });
797
+ }),
798
+ revokeChannel: (teamId, channelId) => safely(async () => {
799
+ need(channelId, "channelId", "channelId");
800
+ const r = await request(
801
+ "DELETE",
802
+ `${teamPath(teamId)}/channels/${enc(channelId)}`
803
+ );
804
+ return { memberships_removed: r.memberships_removed ?? 0 };
805
+ })
806
+ },
807
+ policies: {
808
+ list: (options = {}) => safely(async () => {
809
+ const r = await request(
810
+ "GET",
811
+ "/v1/agents/policies",
812
+ { query: { scope: options.scope } }
813
+ );
814
+ return r.policies ?? [];
815
+ }),
816
+ get: (policyId) => safely(async () => {
817
+ need(policyId, "The policy id", "policyId");
818
+ const r = await request(
819
+ "GET",
820
+ `/v1/agents/policies/${enc(policyId)}`
821
+ );
822
+ return r.policy;
823
+ }),
824
+ put: (input) => safely(async () => {
825
+ const r = await request(
826
+ "PUT",
827
+ "/v1/agents/policies",
828
+ {
829
+ body: {
830
+ scope: input.scope,
831
+ scope_id: input.scopeId,
832
+ rules: input.rules,
833
+ enabled: input.enabled
834
+ }
835
+ }
836
+ );
837
+ return r.policy;
838
+ }),
839
+ delete: (policyId) => safely(async () => {
840
+ need(policyId, "The policy id", "policyId");
841
+ await request("DELETE", `/v1/agents/policies/${enc(policyId)}`);
842
+ }),
843
+ preview: (agentId, options = {}) => safely(() => {
844
+ need(agentId, "agentId", "agentId");
845
+ return request(
846
+ "GET",
847
+ "/v1/agents/policies/preview",
848
+ { query: { agent_id: agentId, channel_id: options.channelId } }
849
+ );
850
+ })
851
+ },
852
+ orgChart: {
853
+ get: (options = {}) => request("GET", "/v1/agents/org-chart", {
854
+ query: {
855
+ team_id: options.teamId,
856
+ channel_id: options.channelId,
857
+ include_retired: options.includeRetired ? "true" : void 0
858
+ }
859
+ }),
860
+ setManager: (agentId, input) => safely(async () => {
861
+ if (input?.managerAgentId && input?.managerUserId) {
862
+ throw new ValidationError(
863
+ "Set managerAgentId or managerUserId, not both",
864
+ "managerAgentId"
865
+ );
866
+ }
867
+ const r = await request(
868
+ "PUT",
869
+ `/v1/agents/org-chart/${agentPath(agentId)}/manager`,
870
+ {
871
+ body: {
872
+ manager_agent_id: input?.managerAgentId ?? null,
873
+ manager_user_id: input?.managerUserId ?? null
874
+ }
875
+ }
876
+ );
877
+ return r.agent;
878
+ }),
879
+ setOwner: (agentId, ownerUserId) => safely(async () => {
880
+ const r = await request(
881
+ "PUT",
882
+ `/v1/agents/org-chart/${agentPath(agentId)}/owner`,
883
+ { body: { owner_user_id: ownerUserId ?? null } }
884
+ );
885
+ return r.agent;
886
+ })
887
+ },
888
+ onboarding: (agentId) => safely(
889
+ () => request(
890
+ "GET",
891
+ `/v1/agents/${agentPath(agentId)}/onboarding`
892
+ )
893
+ ),
894
+ setStatus: (agentId, status, options = {}) => safely(
895
+ () => request(
896
+ "POST",
897
+ `/v1/agents/${agentPath(agentId)}/status`,
898
+ { body: { status, reason: options.reason } }
899
+ )
900
+ )
901
+ };
902
+ }
903
+ var QUESTION_OUTCOMES = /* @__PURE__ */ new Set([
904
+ "question.answered",
905
+ "question.expired",
906
+ "question.canceled"
907
+ ]);
908
+ function isAgentMessageWebhook(payload) {
909
+ return payload?.event === "agent.message";
910
+ }
911
+ function isQuestionOutcomeWebhook(payload) {
912
+ return Boolean(payload && QUESTION_OUTCOMES.has(payload.event));
913
+ }
914
+ function isQuestionEscalatedWebhook(payload) {
915
+ return payload?.event === "question.escalated";
916
+ }
917
+ function isQuestionOutcomeMessage(message) {
918
+ const data = message?.data;
919
+ return Boolean(
920
+ data && typeof data.type === "string" && QUESTION_OUTCOMES.has(data.type) && data.question && typeof data.question === "object"
921
+ );
922
+ }
923
+ function isTaskHandoffMessage(message) {
924
+ const handoff = message?.data?.handoff;
925
+ return Boolean(
926
+ message?.type === "status" && handoff && typeof handoff === "object" && typeof handoff.to_agent_id === "string"
927
+ );
928
+ }
929
+
930
+ // src/agents.ts
931
+ function resolve(config) {
932
+ const env = readChannlizeEnv();
933
+ const apiKey = config.apiKey ?? env.serverApiKey ?? env.apiKey;
934
+ if (!apiKey) {
935
+ throw new ValidationError(
936
+ "An agent API key is required: pass apiKey or set CHANNLIZE_API_KEY (or CHANNLIZE_SERVER_API_KEY)",
937
+ "apiKey"
938
+ );
939
+ }
940
+ if (keyKind(apiKey) === "widget") {
941
+ throw new ValidationError(
942
+ "Agents need an agent key or a multi-agent key (ch_live_\u2026, agents:send / agents:read scopes), not the public widget key",
943
+ "apiKey"
944
+ );
945
+ }
946
+ return {
947
+ apiKey,
948
+ agentId: config.agentId ?? env.agentId,
949
+ baseUrl: (config.baseUrl ?? env.baseUrl ?? DEFAULT_BASE_URL).replace(
950
+ /\/+$/,
951
+ ""
952
+ ),
953
+ timeout: config.timeout ?? DEFAULT_TIMEOUT
954
+ };
955
+ }
956
+ var enc2 = encodeURIComponent;
957
+ function isFatal(error) {
958
+ return error instanceof ChannlizeError && [400, 401, 403, 404].includes(error.statusCode ?? 0);
959
+ }
960
+ function sleep2(ms, signal) {
961
+ return new Promise((done) => {
962
+ if (signal?.aborted) return done();
963
+ const timer = setTimeout(finish, ms);
964
+ function finish() {
965
+ clearTimeout(timer);
966
+ signal?.removeEventListener("abort", finish);
967
+ done();
968
+ }
969
+ signal?.addEventListener("abort", finish, { once: true });
970
+ });
971
+ }
972
+ function createAgentsClient(config = {}) {
973
+ const cfg = resolve(config);
974
+ const request = async (method, path, options = {}) => {
975
+ const params = new URLSearchParams();
976
+ for (const [k, v] of Object.entries(options.query ?? {})) {
977
+ if (v !== void 0 && v !== "") params.set(k, String(v));
978
+ }
979
+ const qs = params.toString();
980
+ const http = { ...cfg, timeout: options.timeout ?? cfg.timeout };
981
+ const res = await requestJson(http, `${path}${qs ? `?${qs}` : ""}`, {
982
+ method,
983
+ headers: { Authorization: `Bearer ${cfg.apiKey}` },
984
+ body: options.body === void 0 ? void 0 : JSON.stringify(options.body),
985
+ signal: options.signal
986
+ });
987
+ return res;
988
+ };
989
+ function agentFor(agentId, what) {
990
+ const id = agentId ?? cfg.agentId;
991
+ if (!id) {
992
+ throw new ValidationError(
993
+ `${what} needs an agent id: pass it or set agentId (CHANNLIZE_AGENT_ID)`,
994
+ "agentId"
995
+ );
996
+ }
997
+ return id;
998
+ }
999
+ async function inbox(agentId, options = {}) {
1000
+ const id = agentFor(agentId, "inbox");
1001
+ const r = await request("GET", `/v1/agents/${enc2(id)}/inbox`, {
1002
+ query: { cursor: options.cursor, limit: options.limit }
1003
+ });
1004
+ return {
1005
+ messages: r.messages ?? [],
1006
+ nextCursor: r.next_cursor ?? null,
1007
+ hasMore: Boolean(r.has_more)
1008
+ };
1009
+ }
1010
+ async function ack(agentId, input) {
1011
+ const id = agentFor(agentId, "ack");
1012
+ let body;
1013
+ if ("upTo" in input && input.upTo) body = { up_to: input.upTo };
1014
+ else if ("ids" in input && input.ids?.length) body = { ids: input.ids };
1015
+ else {
1016
+ throw new ValidationError(
1017
+ "ack needs upTo or a non-empty ids list",
1018
+ "ids"
1019
+ );
1020
+ }
1021
+ return request("POST", `/v1/agents/${enc2(id)}/inbox/ack`, {
1022
+ body
1023
+ });
1024
+ }
1025
+ async function poll(agentId, handler, options = {}) {
1026
+ const id = agentFor(agentId, "poll");
1027
+ const interval = Math.max(0, options.intervalMs ?? 5e3);
1028
+ const maxBackoff = Math.max(interval, options.maxBackoffMs ?? 6e4);
1029
+ const { signal } = options;
1030
+ let failures = 0;
1031
+ while (!signal?.aborted) {
1032
+ let failed = false;
1033
+ let more = false;
1034
+ try {
1035
+ const page = await inbox(id, { limit: options.limit });
1036
+ more = page.hasMore;
1037
+ const done = [];
1038
+ try {
1039
+ for (const item of page.messages) {
1040
+ if (signal?.aborted) break;
1041
+ try {
1042
+ await handler(item.message, item);
1043
+ } catch (error) {
1044
+ failed = true;
1045
+ options.onError?.(error, item);
1046
+ break;
1047
+ }
1048
+ done.push(item.delivery_id);
1049
+ }
1050
+ } finally {
1051
+ if (done.length) await ack(id, { ids: done });
1052
+ }
1053
+ } catch (error) {
1054
+ if (isFatal(error)) throw error;
1055
+ failed = true;
1056
+ options.onError?.(error);
1057
+ }
1058
+ if (failed) {
1059
+ failures += 1;
1060
+ const backoff = Math.min(
1061
+ maxBackoff,
1062
+ (interval || 1e3) * 2 ** (failures - 1)
1063
+ );
1064
+ await sleep2(backoff, signal);
1065
+ } else {
1066
+ failures = 0;
1067
+ if (!more) await sleep2(interval, signal);
1068
+ }
1069
+ }
1070
+ }
1071
+ const channelRead = (options = {}) => ({
1072
+ agent_id: options.agentId ?? cfg.agentId
1073
+ });
1074
+ return {
1075
+ ...createAgentsGovernance(request, cfg.agentId),
1076
+ agentId: cfg.agentId,
1077
+ send(input) {
1078
+ if (!input || typeof input.channel !== "string" || !input.channel) {
1079
+ return Promise.reject(
1080
+ new ValidationError("send needs a channel id", "channel")
1081
+ );
1082
+ }
1083
+ return request("POST", "/v1/agents/messages", {
1084
+ body: {
1085
+ agent_id: input.agentId ?? cfg.agentId,
1086
+ channel_id: input.channel,
1087
+ type: input.type,
1088
+ text: input.text,
1089
+ thread_id: input.threadId,
1090
+ mentions: input.mentions,
1091
+ data: input.data,
1092
+ tokens: input.tokens,
1093
+ cost: input.cost,
1094
+ task_id: input.taskId,
1095
+ task_status: input.taskStatus
1096
+ }
1097
+ });
1098
+ },
1099
+ inbox,
1100
+ ack,
1101
+ poll,
1102
+ async list(options = {}) {
1103
+ const r = await request("GET", "/v1/agents", {
1104
+ query: { include_retired: options.includeRetired || void 0 }
1105
+ });
1106
+ return r.agents ?? [];
1107
+ },
1108
+ get(agentId) {
1109
+ return request("GET", `/v1/agents/${enc2(agentId)}`);
1110
+ },
1111
+ channels: {
1112
+ async list(options = {}) {
1113
+ const r = await request(
1114
+ "GET",
1115
+ "/v1/agents/channels",
1116
+ { query: { include_archived: options.includeArchived || void 0 } }
1117
+ );
1118
+ return r.channels ?? [];
1119
+ },
1120
+ get(channelId, options) {
1121
+ return request("GET", `/v1/agents/channels/${enc2(channelId)}`, {
1122
+ query: channelRead(options)
1123
+ });
1124
+ },
1125
+ async members(channelId, options) {
1126
+ const r = await request(
1127
+ "GET",
1128
+ `/v1/agents/channels/${enc2(channelId)}/members`,
1129
+ { query: channelRead(options) }
1130
+ );
1131
+ return r.members ?? [];
1132
+ },
1133
+ async messages(channelId, options = {}) {
1134
+ const r = await request("GET", `/v1/agents/channels/${enc2(channelId)}/messages`, {
1135
+ query: {
1136
+ ...channelRead(options),
1137
+ cursor: options.cursor,
1138
+ after: options.after,
1139
+ limit: options.limit,
1140
+ sender_agent_id: options.senderAgentId,
1141
+ type: options.type,
1142
+ thread_id: options.threadId
1143
+ }
1144
+ });
1145
+ return {
1146
+ channel: r.channel,
1147
+ messages: r.messages ?? [],
1148
+ nextCursor: r.next_cursor ?? null,
1149
+ latestCursor: r.latest_cursor ?? null,
1150
+ hasMore: Boolean(r.has_more)
1151
+ };
1152
+ }
1153
+ }
1154
+ };
1155
+ }
1156
+ var NODE_CRYPTO = "node:crypto";
1157
+ async function getSubtle() {
1158
+ const subtle = globalThis.crypto?.subtle;
1159
+ if (subtle) return subtle;
1160
+ try {
1161
+ const mod = await import(
1162
+ /* webpackIgnore: true */
1163
+ /* @vite-ignore */
1164
+ NODE_CRYPTO
1165
+ );
1166
+ if (mod.webcrypto?.subtle) return mod.webcrypto.subtle;
1167
+ } catch {
1168
+ }
1169
+ throw new ChannlizeError(
1170
+ "verifyAgentWebhook needs the Web Crypto API (globalThis.crypto.subtle, or node:crypto webcrypto on Node.js 18)"
1171
+ );
1172
+ }
1173
+ function hexToBytes(hex) {
1174
+ if (hex.length === 0 || hex.length % 2 !== 0 || /[^0-9a-f]/i.test(hex)) {
1175
+ return null;
1176
+ }
1177
+ const out = new Uint8Array(hex.length / 2);
1178
+ for (let i = 0; i < out.length; i++) {
1179
+ out[i] = parseInt(hex.slice(i * 2, i * 2 + 2), 16);
1180
+ }
1181
+ return out;
1182
+ }
1183
+ function toBytes(body) {
1184
+ if (typeof body === "string") return new TextEncoder().encode(body);
1185
+ const copy = new Uint8Array(body.byteLength);
1186
+ copy.set(body);
1187
+ return copy;
1188
+ }
1189
+ async function verifyAgentWebhook(rawBody, signatureHeader, secret) {
1190
+ if (!secret)
1191
+ throw new ValidationError("The agent secret is required", "secret");
1192
+ const header = Array.isArray(signatureHeader) ? signatureHeader[0] : signatureHeader;
1193
+ const m = /^sha256=([0-9a-f]+)$/i.exec(header?.trim() ?? "");
1194
+ const expected = m ? hexToBytes(m[1]) : null;
1195
+ if (!expected || expected.length !== 32) return false;
1196
+ const subtle = await getSubtle();
1197
+ const key = await subtle.importKey(
1198
+ "raw",
1199
+ new TextEncoder().encode(secret),
1200
+ { name: "HMAC", hash: "SHA-256" },
1201
+ false,
1202
+ ["verify"]
1203
+ );
1204
+ return subtle.verify("HMAC", key, expected, toBytes(rawBody));
1205
+ }
1206
+ async function parseAgentWebhook(rawBody, signatureHeader, secret) {
1207
+ return await parseAgentWebhookEvent(
1208
+ rawBody,
1209
+ signatureHeader,
1210
+ secret
1211
+ );
1212
+ }
1213
+ async function parseAgentWebhookEvent(rawBody, signatureHeader, secret) {
1214
+ if (!await verifyAgentWebhook(rawBody, signatureHeader, secret)) {
1215
+ throw new ChannlizeError(
1216
+ "Invalid agent webhook signature",
1217
+ 401,
1218
+ "INVALID_SIGNATURE"
1219
+ );
1220
+ }
1221
+ const text = typeof rawBody === "string" ? rawBody : new TextDecoder().decode(rawBody);
1222
+ return JSON.parse(text);
1223
+ }
1224
+
212
1225
  // src/client.ts
213
1226
  var ChannlizeClient = class {
214
1227
  config;
@@ -216,8 +1229,8 @@ var ChannlizeClient = class {
216
1229
  this.config = {
217
1230
  ...config,
218
1231
  // After the spread: an explicit `undefined` must not drop the defaults.
219
- baseUrl: config.baseUrl || "https://api.channlize.com",
220
- timeout: config.timeout ?? 1e4
1232
+ baseUrl: config.baseUrl || DEFAULT_BASE_URL,
1233
+ timeout: config.timeout ?? DEFAULT_TIMEOUT
221
1234
  };
222
1235
  this.validateConfig();
223
1236
  }
@@ -226,6 +1239,26 @@ var ChannlizeClient = class {
226
1239
  throw new ValidationError("Widget key is required");
227
1240
  }
228
1241
  }
1242
+ agentsClient;
1243
+ /**
1244
+ * Channlize for Agents (`agents.send`, `agents.inbox`, `agents.poll`, ...),
1245
+ * authenticated with `serverApiKey`: an agent key or a multi-agent key.
1246
+ * Same as `createAgentsClient({ apiKey: serverApiKey, baseUrl, timeout })`.
1247
+ */
1248
+ get agents() {
1249
+ if (!this.config.serverApiKey) {
1250
+ throw new ValidationError(
1251
+ "serverApiKey (an agent or multi-agent key) is required for agents",
1252
+ "serverApiKey"
1253
+ );
1254
+ }
1255
+ this.agentsClient ??= createAgentsClient({
1256
+ apiKey: this.config.serverApiKey,
1257
+ baseUrl: this.config.baseUrl,
1258
+ timeout: this.config.timeout
1259
+ });
1260
+ return this.agentsClient;
1261
+ }
229
1262
  makeRequest(endpoint, options = {}) {
230
1263
  return requestJson(this.config, endpoint, options);
231
1264
  }
@@ -343,6 +1376,9 @@ var ChannlizeClient = class {
343
1376
  * `POST /public/events`). Shown under Log tracking in the dashboard.
344
1377
  */
345
1378
  async trackEvent(name, options) {
1379
+ return this.nonFatalTracking(() => this.sendBrowserEvent(name, options));
1380
+ }
1381
+ async sendBrowserEvent(name, options) {
346
1382
  const res = await this.makeRequest(
347
1383
  "/public/events",
348
1384
  {
@@ -371,6 +1407,28 @@ var ChannlizeClient = class {
371
1407
  "serverApiKey is required for trackServerEvent"
372
1408
  );
373
1409
  }
1410
+ return this.nonFatalTracking(() => this.sendServerEvent(name, options));
1411
+ }
1412
+ /**
1413
+ * Log tracking must never break the caller: a 429 (rate limit, or the
1414
+ * plan's limit for the billing period) resolves with `success: false`
1415
+ * and `dropped` instead of throwing, with one console warning per client.
1416
+ */
1417
+ async nonFatalTracking(send) {
1418
+ try {
1419
+ return await send();
1420
+ } catch (error) {
1421
+ if (!(error instanceof RateLimitError)) throw error;
1422
+ const dropped = error.code === "PLAN_LIMIT_REACHED" ? "plan_limit" : "rate_limit";
1423
+ if (!this.warnedDropped && typeof console !== "undefined") {
1424
+ this.warnedDropped = true;
1425
+ console.warn(`[channlize] log event dropped: ${error.message}`);
1426
+ }
1427
+ return { success: false, id: "", dropped };
1428
+ }
1429
+ }
1430
+ warnedDropped = false;
1431
+ async sendServerEvent(name, options) {
374
1432
  const res = await this.makeRequest(
375
1433
  "/v1/events",
376
1434
  {
@@ -415,13 +1473,75 @@ var ChannlizeClient = class {
415
1473
  );
416
1474
  }
417
1475
  };
1476
+
1477
+ // src/agents-types.ts
1478
+ var AGENT_STATUSES = ["active", "paused", "retired"];
1479
+ var AGENT_DELIVERY_MODES = ["webhook", "inbox", "a2a"];
1480
+ var AGENT_MESSAGE_TYPES = [
1481
+ "message",
1482
+ "task",
1483
+ "question",
1484
+ "escalation",
1485
+ "status"
1486
+ ];
1487
+ var SENDABLE_AGENT_MESSAGE_TYPES = [
1488
+ "message",
1489
+ "task",
1490
+ "status"
1491
+ ];
1492
+ var AGENT_TASK_STATUSES = [
1493
+ "open",
1494
+ "working",
1495
+ "input_required",
1496
+ "completed",
1497
+ "failed",
1498
+ "canceled",
1499
+ "rejected"
1500
+ ];
418
1501
  // Annotate the CommonJS export names for ESM import in node:
419
1502
  0 && (module.exports = {
1503
+ AGENT_DELIVERY_MODES,
1504
+ AGENT_MESSAGE_TYPES,
1505
+ AGENT_POLICY_CODES,
1506
+ AGENT_STATUSES,
1507
+ AGENT_TASK_ERROR_CODES,
1508
+ AGENT_TASK_STATUSES,
1509
+ AgentPolicyError,
1510
+ AgentTaskError,
1511
+ AgentThreadPausedError,
420
1512
  AuthenticationError,
421
1513
  ChannlizeClient,
422
1514
  ChannlizeError,
1515
+ DEFAULT_BASE_URL,
1516
+ DEFAULT_TIMEOUT,
1517
+ EVENT_KEY_PATTERN,
1518
+ EVENT_SEVERITIES,
1519
+ HumanTakeoverError,
1520
+ POLICY_SCOPES,
1521
+ QUESTION_KINDS,
1522
+ QUESTION_STATUSES,
1523
+ QUESTION_URGENCIES,
1524
+ QUESTION_WAIT_MAX_SECONDS,
1525
+ QuestionClosedError,
423
1526
  RateLimitError,
1527
+ SENDABLE_AGENT_MESSAGE_TYPES,
1528
+ TASK_BOARD_STATUSES,
424
1529
  ValidationError,
425
- createNotifier
1530
+ createAgentsClient,
1531
+ createNotifier,
1532
+ isAgentMessageWebhook,
1533
+ isQuestionEscalatedWebhook,
1534
+ isQuestionOutcomeMessage,
1535
+ isQuestionOutcomeWebhook,
1536
+ isRetryable,
1537
+ isTaskHandoffMessage,
1538
+ isValidEventKey,
1539
+ keyKind,
1540
+ parseAgentWebhook,
1541
+ parseAgentWebhookEvent,
1542
+ parseApiError,
1543
+ readChannlizeEnv,
1544
+ retryAfter,
1545
+ verifyAgentWebhook
426
1546
  });
427
1547
  //# sourceMappingURL=index.js.map