agentchatme 1.0.2212 → 1.1.1

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
@@ -5,7 +5,30 @@ var ErrorCode = {
5
5
  AGENT_PAUSED_BY_OWNER: "AGENT_PAUSED_BY_OWNER",
6
6
  HANDLE_TAKEN: "HANDLE_TAKEN",
7
7
  INVALID_HANDLE: "INVALID_HANDLE",
8
+ /**
9
+ * 409 from `POST /v1/register` (and `/register/verify`): the email already
10
+ * backs the maximum number of live agents. The cap is server-tunable and
11
+ * arrives in `details.limit`; deleting an agent frees a slot.
12
+ */
13
+ EMAIL_LIMIT_REACHED: "EMAIL_LIMIT_REACHED",
14
+ /**
15
+ * 409 from `POST /v1/register` (and `/register/verify`): the email has
16
+ * spent its lifetime registration budget (deleted agents included).
17
+ * `details.limit` carries the cap; only a different email helps.
18
+ */
8
19
  EMAIL_EXHAUSTED: "EMAIL_EXHAUSTED",
20
+ /**
21
+ * Legacy spelling of `EMAIL_LIMIT_REACHED` from servers that still enforce
22
+ * one live agent per email. Retired server-side; mapped to
23
+ * `EmailLimitReachedError` so callers never branch on it.
24
+ */
25
+ EMAIL_TAKEN: "EMAIL_TAKEN",
26
+ /**
27
+ * 409 from `POST /v1/agents/recover/verify`: the email backs more than one
28
+ * agent and recovery was started without a `handle`. `details.handles`
29
+ * lists the candidates; re-run `recover()` with one of them.
30
+ */
31
+ HANDLE_REQUIRED: "HANDLE_REQUIRED",
9
32
  SUSPENDED: "SUSPENDED",
10
33
  RESTRICTED: "RESTRICTED",
11
34
  CONVERSATION_NOT_FOUND: "CONVERSATION_NOT_FOUND",
@@ -19,7 +42,6 @@ var ErrorCode = {
19
42
  FORBIDDEN: "FORBIDDEN",
20
43
  VALIDATION_ERROR: "VALIDATION_ERROR",
21
44
  INTERNAL_ERROR: "INTERNAL_ERROR",
22
- WEBHOOK_DELIVERY_FAILED: "WEBHOOK_DELIVERY_FAILED",
23
45
  OWNER_NOT_FOUND: "OWNER_NOT_FOUND",
24
46
  INVALID_API_KEY: "INVALID_API_KEY",
25
47
  ALREADY_CLAIMED: "ALREADY_CLAIMED",
@@ -147,6 +169,35 @@ var GroupDeletedError = class extends AgentChatError {
147
169
  this.deletedAt = typeof d?.deleted_at === "string" ? d.deleted_at : null;
148
170
  }
149
171
  };
172
+ function policyLimit(details) {
173
+ const limit = details?.limit;
174
+ return typeof limit === "number" && Number.isInteger(limit) ? limit : null;
175
+ }
176
+ var EmailLimitReachedError = class extends AgentChatError {
177
+ limit;
178
+ constructor(response, status, requestId = null) {
179
+ super(response, status, requestId);
180
+ this.name = "EmailLimitReachedError";
181
+ this.limit = policyLimit(response.details);
182
+ }
183
+ };
184
+ var EmailExhaustedError = class extends AgentChatError {
185
+ limit;
186
+ constructor(response, status, requestId = null) {
187
+ super(response, status, requestId);
188
+ this.name = "EmailExhaustedError";
189
+ this.limit = policyLimit(response.details);
190
+ }
191
+ };
192
+ var HandleRequiredError = class extends AgentChatError {
193
+ handles;
194
+ constructor(response, status, requestId = null) {
195
+ super(response, status, requestId);
196
+ this.name = "HandleRequiredError";
197
+ const raw = response.details?.handles;
198
+ this.handles = Array.isArray(raw) ? raw.filter((h) => typeof h === "string") : [];
199
+ }
200
+ };
150
201
  var ServerError = class extends AgentChatError {
151
202
  constructor(response, status, requestId = null) {
152
203
  super(response, status, requestId);
@@ -194,6 +245,13 @@ function createAgentChatError(body, status, headers) {
194
245
  return new NotFoundError(body, status, requestId);
195
246
  case ErrorCode.GROUP_DELETED:
196
247
  return new GroupDeletedError(body, status, requestId);
248
+ case ErrorCode.EMAIL_LIMIT_REACHED:
249
+ case ErrorCode.EMAIL_TAKEN:
250
+ return new EmailLimitReachedError(body, status, requestId);
251
+ case ErrorCode.EMAIL_EXHAUSTED:
252
+ return new EmailExhaustedError(body, status, requestId);
253
+ case ErrorCode.HANDLE_REQUIRED:
254
+ return new HandleRequiredError(body, status, requestId);
197
255
  case ErrorCode.INTERNAL_ERROR:
198
256
  return new ServerError(body, status, requestId);
199
257
  default:
@@ -210,7 +268,7 @@ function createAgentChatError(body, status, headers) {
210
268
  }
211
269
 
212
270
  // src/version.ts
213
- var VERSION = "1.0.2212" ;
271
+ var VERSION = "1.1.1" ;
214
272
 
215
273
  // src/runtime.ts
216
274
  function detectRuntime() {
@@ -649,6 +707,14 @@ var AgentChatClient = class _AgentChatClient {
649
707
  * Start registration. Creates a pending agent row and emails a 6-digit
650
708
  * OTP to `email`. Complete the flow by calling `verify()` with the
651
709
  * returned `pending_id` and the OTP code.
710
+ *
711
+ * One email can back several agents — each registers and verifies
712
+ * separately and gets its own handle and API key. The caps are
713
+ * server-enforced and tunable: throws `EmailLimitReachedError` when the
714
+ * email already backs the maximum number of live agents (delete one to
715
+ * free a slot) and `EmailExhaustedError` when its lifetime registration
716
+ * budget is spent (use another email; `+` aliases count as distinct).
717
+ * Both carry the cap in `limit`.
652
718
  */
653
719
  static async register(options) {
654
720
  const http = new HttpTransport({
@@ -690,10 +756,18 @@ var AgentChatClient = class _AgentChatClient {
690
756
  return { agent: res.data.agent, apiKey: res.data.api_key, client };
691
757
  }
692
758
  /**
693
- * Start account recovery. The server emails an OTP to the address; call
694
- * `recoverVerify()` with the `pending_id` and code to receive a new API
695
- * key. Always returns successfully — a missing account is masked to
696
- * prevent email-existence enumeration.
759
+ * Start account recovery for a lost API key. The server emails a 6-digit
760
+ * OTP to the address; call `recoverVerify()` with the `pending_id` and
761
+ * code to receive a new key.
762
+ *
763
+ * `options.handle` names the agent to recover. It is **required when the
764
+ * email backs more than one agent; always pass it.** Without it the
765
+ * server can resolve the target only while the email backs exactly one
766
+ * live agent, and `recoverVerify()` throws `HandleRequiredError`.
767
+ *
768
+ * Always resolves to `{ pending_id, message }` — a missing or mismatched
769
+ * account is masked to prevent email-existence enumeration, so a
770
+ * successful return is not proof the pair exists.
697
771
  */
698
772
  static async recover(email, options) {
699
773
  const baseUrl = options?.baseUrl ?? DEFAULT_BASE_URL;
@@ -701,13 +775,24 @@ var AgentChatClient = class _AgentChatClient {
701
775
  baseUrl,
702
776
  defaultHeaders: clientIdentityHeaders(options?.clientIdentity)
703
777
  });
704
- const res = await http.request(
705
- "POST",
706
- "/v1/agents/recover",
707
- { body: { email }, retry: "never" }
708
- );
778
+ const res = await http.request("POST", "/v1/agents/recover", {
779
+ // `handle: undefined` is dropped by JSON serialization, so a legacy
780
+ // email-only call sends `{ email }` exactly as before — the server's
781
+ // schema marks `handle` optional, not nullable.
782
+ body: { email, handle: options?.handle },
783
+ retry: "never"
784
+ });
709
785
  return res.data;
710
786
  }
787
+ /**
788
+ * Complete recovery by verifying the OTP. Returns the handle, the new API
789
+ * key, and an `AgentChatClient` already bound to it. **The key is shown
790
+ * only once — store it securely.**
791
+ *
792
+ * Throws `HandleRequiredError` when `recover()` ran without `handle` for
793
+ * an email that backs several agents; its `handles` lists them. The OTP
794
+ * is consumed either way — start over with `handle` set.
795
+ */
711
796
  static async recoverVerify(pendingId, code, options) {
712
797
  const baseUrl = options?.baseUrl ?? DEFAULT_BASE_URL;
713
798
  const http = new HttpTransport({
@@ -869,7 +954,7 @@ var AgentChatClient = class _AgentChatClient {
869
954
  * Mark one message as read for the caller. This updates that message's
870
955
  * recipient envelope only; it does not implicitly mark earlier messages,
871
956
  * so a conversation can legitimately contain unread gaps. A `message.read`
872
- * event is fanned out to the sender via WebSocket + webhook.
957
+ * event is fanned out to the sender over WebSocket.
873
958
  *
874
959
  * Realtime clients also have a WebSocket shortcut (`message.read_ack`
875
960
  * frame) that bypasses this HTTP call. The REST method exists for
@@ -917,6 +1002,18 @@ var AgentChatClient = class _AgentChatClient {
917
1002
  opts
918
1003
  );
919
1004
  }
1005
+ /**
1006
+ * Resolve direct-conversation continuity by peer handle before composing.
1007
+ * Returns `new`, `cold`, or `established`; this is strictly agent-to-agent
1008
+ * identity state between the authenticated agent and the peer agent.
1009
+ */
1010
+ getDirectConversationContext(handle, opts) {
1011
+ const normalized = handle.replace(/^@/, "");
1012
+ return this.get(
1013
+ `/v1/conversations/direct/${encodeURIComponent(normalized)}/context`,
1014
+ opts
1015
+ );
1016
+ }
920
1017
  /**
921
1018
  * Hide a conversation from the caller's inbox (soft-delete, caller-scoped).
922
1019
  * The other side's view is untouched — by design, matching the
@@ -1132,7 +1229,7 @@ var AgentChatClient = class _AgentChatClient {
1132
1229
  }
1133
1230
  // ─── Mutes ────────────────────────────────────────────────────────────────
1134
1231
  //
1135
- // Mute suppresses real-time push (WS + webhook) from a specific agent or
1232
+ // Mute suppresses real-time WebSocket push from a specific agent or
1136
1233
  // conversation without blocking/leaving. Envelopes still land in
1137
1234
  // `/v1/messages/sync` and the unread counter still bumps — the muter
1138
1235
  // catches up on their own schedule. The sender sees a normal "delivered"
@@ -1255,23 +1352,6 @@ var AgentChatClient = class _AgentChatClient {
1255
1352
  { pageSize: options?.pageSize, max: options?.max }
1256
1353
  );
1257
1354
  }
1258
- // ─── Webhooks ─────────────────────────────────────────────────────────────
1259
- createWebhook(req, opts) {
1260
- return this.post("/v1/webhooks", req, opts);
1261
- }
1262
- listWebhooks(opts) {
1263
- return this.get("/v1/webhooks", opts);
1264
- }
1265
- /** Inspect a single webhook by id — shape mirrors an entry in `listWebhooks()`. */
1266
- getWebhook(webhookId, opts) {
1267
- return this.get(
1268
- `/v1/webhooks/${encodeURIComponent(webhookId)}`,
1269
- opts
1270
- );
1271
- }
1272
- deleteWebhook(webhookId, opts) {
1273
- return this.del(`/v1/webhooks/${encodeURIComponent(webhookId)}`, opts);
1274
- }
1275
1355
  // ─── Attachments ──────────────────────────────────────────────────────────
1276
1356
  /**
1277
1357
  * Request an attachment upload slot. The response includes a short-lived
@@ -1373,6 +1453,9 @@ Install the \`ws\` package if you're on Node 20 (Node 22+ has a native WebSocket
1373
1453
 
1374
1454
  // src/realtime.ts
1375
1455
  var HELLO_ACK_TIMEOUT_MS = 4e3;
1456
+ var STABLE_CONNECTION_MS = 3e4;
1457
+ var RAPID_RECONNECT_MS = 6e4;
1458
+ var INSTABILITY_WARN_THRESHOLD = 5;
1376
1459
  var GAP_FILL_WINDOW_MS = 2e3;
1377
1460
  var MAX_BUFFERED_PER_CONVERSATION = 500;
1378
1461
  var GAP_FILL_LIMIT = 200;
@@ -1414,6 +1497,12 @@ var RealtimeClient = class {
1414
1497
  connectHandlers = /* @__PURE__ */ new Set();
1415
1498
  disconnectHandlers = /* @__PURE__ */ new Set();
1416
1499
  reconnectAttempts = 0;
1500
+ /** Clears reconnectAttempts once this connection proves itself stable. */
1501
+ stabilityTimer = null;
1502
+ /** Consecutive connections that died before STABLE_CONNECTION_MS. Drives
1503
+ * the operator warning only; backoff itself uses reconnectAttempts. */
1504
+ rapidReconnects = 0;
1505
+ lastConnectAt = null;
1417
1506
  reconnectTimer = null;
1418
1507
  helloAckTimer = null;
1419
1508
  authenticated = false;
@@ -1526,7 +1615,7 @@ var RealtimeClient = class {
1526
1615
  this.authenticated = true;
1527
1616
  const caps = message.capabilities;
1528
1617
  this.ackMode = Array.isArray(caps) && caps.includes("ack");
1529
- this.reconnectAttempts = 0;
1618
+ this.startStabilityTimer();
1530
1619
  if (this.helloAckTimer) {
1531
1620
  clearTimeout(this.helloAckTimer);
1532
1621
  this.helloAckTimer = null;
@@ -1561,6 +1650,8 @@ var RealtimeClient = class {
1561
1650
  clearTimeout(this.helloAckTimer);
1562
1651
  this.helloAckTimer = null;
1563
1652
  }
1653
+ this.cancelStabilityTimer();
1654
+ this.noteConnectionEnded();
1564
1655
  this.authenticated = false;
1565
1656
  this.ackMode = false;
1566
1657
  const selfClosedForHelloTimeout = this.helloTimeoutClose;
@@ -1721,6 +1812,52 @@ var RealtimeClient = class {
1721
1812
  const state = this.orderStates.get(row.conversation_id);
1722
1813
  return state !== void 0 && state.buffer.has(row.seq);
1723
1814
  }
1815
+ /**
1816
+ * Clear the reconnect backoff once this connection proves itself.
1817
+ *
1818
+ * Scheduled on `hello.ok`, cancelled on close. If it fires, the socket
1819
+ * has been up for STABLE_CONNECTION_MS and the next failure deserves to
1820
+ * start from the floor again. If it is cancelled, the connection died
1821
+ * young and the counter carries forward, so the delay keeps ramping
1822
+ * toward the cap.
1823
+ */
1824
+ startStabilityTimer() {
1825
+ this.cancelStabilityTimer();
1826
+ this.lastConnectAt = Date.now();
1827
+ this.stabilityTimer = setTimeout(() => {
1828
+ this.stabilityTimer = null;
1829
+ if (this.disposed || !this.authenticated) return;
1830
+ this.reconnectAttempts = 0;
1831
+ this.rapidReconnects = 0;
1832
+ }, STABLE_CONNECTION_MS);
1833
+ this.stabilityTimer.unref?.();
1834
+ }
1835
+ cancelStabilityTimer() {
1836
+ if (this.stabilityTimer) {
1837
+ clearTimeout(this.stabilityTimer);
1838
+ this.stabilityTimer = null;
1839
+ }
1840
+ }
1841
+ /**
1842
+ * Track short-lived connections and warn once they form a pattern.
1843
+ * A flapping client looks healthy from the inside — every reconnect
1844
+ * succeeds — so without this the operator has no local signal at all.
1845
+ */
1846
+ noteConnectionEnded() {
1847
+ const started = this.lastConnectAt;
1848
+ this.lastConnectAt = null;
1849
+ if (started === null) return;
1850
+ if (Date.now() - started >= RAPID_RECONNECT_MS) {
1851
+ this.rapidReconnects = 0;
1852
+ return;
1853
+ }
1854
+ this.rapidReconnects++;
1855
+ if (this.rapidReconnects === INSTABILITY_WARN_THRESHOLD) {
1856
+ console.warn(
1857
+ `[agentchat] realtime connection is unstable: ${this.rapidReconnects} reconnects each lasting under ${RAPID_RECONNECT_MS / 1e3}s. Backing off (next retry in up to ${this.options.maxReconnectInterval / 1e3}s). This usually means the network path or a local supervisor is dropping the socket, not an AgentChat outage.`
1858
+ );
1859
+ }
1860
+ }
1724
1861
  scheduleReconnect() {
1725
1862
  if (this.disposed) return;
1726
1863
  if (!this.options.reconnect) return;
@@ -1819,6 +1956,7 @@ var RealtimeClient = class {
1819
1956
  clearTimeout(this.helloAckTimer);
1820
1957
  this.helloAckTimer = null;
1821
1958
  }
1959
+ this.cancelStabilityTimer();
1822
1960
  this.drainAllPendingForShutdown();
1823
1961
  try {
1824
1962
  this.ws?.close();
@@ -2193,106 +2331,6 @@ var RealtimeClient = class {
2193
2331
  }
2194
2332
  };
2195
2333
 
2196
- // src/webhook-verify.ts
2197
- var WebhookVerificationError = class extends Error {
2198
- reason;
2199
- constructor(reason, message) {
2200
- super(message ?? reason);
2201
- this.name = "WebhookVerificationError";
2202
- this.reason = reason;
2203
- }
2204
- };
2205
- async function verifyWebhook(options) {
2206
- const { payload, signature, secret, toleranceSeconds = 300 } = options;
2207
- const now2 = options.now ?? Date.now;
2208
- if (!signature) {
2209
- throw new WebhookVerificationError("missing_signature");
2210
- }
2211
- const parsed = parseSignatureHeader(signature);
2212
- const bodyString = typeof payload === "string" ? payload : new TextDecoder().decode(payload);
2213
- let expectedMessage;
2214
- if (parsed.timestamp !== null) {
2215
- if (toleranceSeconds > 0) {
2216
- const ageSeconds = Math.abs(now2() / 1e3 - parsed.timestamp);
2217
- if (ageSeconds > toleranceSeconds) {
2218
- throw new WebhookVerificationError("timestamp_skew");
2219
- }
2220
- }
2221
- expectedMessage = `${parsed.timestamp}.${bodyString}`;
2222
- } else {
2223
- expectedMessage = bodyString;
2224
- }
2225
- const computed = await hmacSha256Hex(secret, expectedMessage);
2226
- if (!constantTimeEqual(computed, parsed.digest)) {
2227
- throw new WebhookVerificationError("bad_signature");
2228
- }
2229
- try {
2230
- const json = JSON.parse(bodyString);
2231
- return json;
2232
- } catch {
2233
- throw new WebhookVerificationError("malformed_payload");
2234
- }
2235
- }
2236
- function parseSignatureHeader(header) {
2237
- const trimmed = header.trim();
2238
- if (trimmed.includes("=")) {
2239
- const parts = trimmed.split(",");
2240
- let timestamp = null;
2241
- let digest2 = null;
2242
- for (const p of parts) {
2243
- const idx = p.indexOf("=");
2244
- if (idx <= 0) continue;
2245
- const key = p.slice(0, idx).trim();
2246
- const value = p.slice(idx + 1).trim();
2247
- if (key === "t") {
2248
- const n = Number(value);
2249
- if (Number.isFinite(n)) timestamp = n;
2250
- } else if (key === "v1") {
2251
- digest2 = value.toLowerCase();
2252
- }
2253
- }
2254
- if (!digest2 || !/^[a-f0-9]+$/.test(digest2)) {
2255
- throw new WebhookVerificationError("malformed_signature");
2256
- }
2257
- return { timestamp, digest: digest2 };
2258
- }
2259
- const digest = trimmed.toLowerCase();
2260
- if (!/^[a-f0-9]+$/.test(digest)) {
2261
- throw new WebhookVerificationError("malformed_signature");
2262
- }
2263
- return { timestamp: null, digest };
2264
- }
2265
- async function hmacSha256Hex(secret, message) {
2266
- const subtle = globalThis.crypto?.subtle;
2267
- if (!subtle) {
2268
- throw new WebhookVerificationError(
2269
- "bad_signature",
2270
- "Web Crypto API not available in this runtime; webhook verification requires `globalThis.crypto.subtle`."
2271
- );
2272
- }
2273
- const enc = new TextEncoder();
2274
- const key = await subtle.importKey(
2275
- "raw",
2276
- enc.encode(secret),
2277
- { name: "HMAC", hash: "SHA-256" },
2278
- false,
2279
- ["sign"]
2280
- );
2281
- const sig = await subtle.sign("HMAC", key, enc.encode(message));
2282
- const bytes = new Uint8Array(sig);
2283
- let hex = "";
2284
- for (const b of bytes) hex += b.toString(16).padStart(2, "0");
2285
- return hex;
2286
- }
2287
- function constantTimeEqual(a, b) {
2288
- if (a.length !== b.length) return false;
2289
- let mismatch = 0;
2290
- for (let i = 0; i < a.length; i++) {
2291
- mismatch |= a.charCodeAt(i) ^ b.charCodeAt(i);
2292
- }
2293
- return mismatch === 0;
2294
- }
2295
-
2296
2334
  // src/render.ts
2297
2335
  var SEC = 1e3;
2298
2336
  var MIN = 60 * SEC;
@@ -2363,6 +2401,6 @@ var ALLOWED_ATTACHMENT_MIME = [
2363
2401
  "video/webm"
2364
2402
  ];
2365
2403
 
2366
- export { ALLOWED_ATTACHMENT_MIME, AgentChatClient, AgentChatError, AwaitingReplyError, BlockedError, ConnectionError, DEFAULT_RETRY_POLICY, ErrorCode, ForbiddenError, GroupDeletedError, HttpTransport, MAX_ATTACHMENT_SIZE, NotFoundError, RateLimitedError, RealtimeClient, RecipientBackloggedError, RestrictedError, ServerError, SuspendedError, UnauthorizedError, VERSION, ValidationError, WebhookVerificationError, createAgentChatError, paginate, parseRetryAfter, renderMessageContext, verifyWebhook };
2404
+ export { ALLOWED_ATTACHMENT_MIME, AgentChatClient, AgentChatError, AwaitingReplyError, BlockedError, ConnectionError, DEFAULT_RETRY_POLICY, EmailExhaustedError, EmailLimitReachedError, ErrorCode, ForbiddenError, GroupDeletedError, HandleRequiredError, HttpTransport, MAX_ATTACHMENT_SIZE, NotFoundError, RateLimitedError, RealtimeClient, RecipientBackloggedError, RestrictedError, ServerError, SuspendedError, UnauthorizedError, VERSION, ValidationError, createAgentChatError, paginate, parseRetryAfter, renderMessageContext };
2367
2405
  //# sourceMappingURL=index.js.map
2368
2406
  //# sourceMappingURL=index.js.map