agentchatme 1.0.2211 → 1.1.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/CHANGELOG.md +35 -0
- package/README.md +10 -52
- package/dist/index.cjs +101 -129
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +100 -92
- package/dist/index.d.ts +100 -92
- package/dist/index.js +102 -128
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -19,7 +19,6 @@ var ErrorCode = {
|
|
|
19
19
|
FORBIDDEN: "FORBIDDEN",
|
|
20
20
|
VALIDATION_ERROR: "VALIDATION_ERROR",
|
|
21
21
|
INTERNAL_ERROR: "INTERNAL_ERROR",
|
|
22
|
-
WEBHOOK_DELIVERY_FAILED: "WEBHOOK_DELIVERY_FAILED",
|
|
23
22
|
OWNER_NOT_FOUND: "OWNER_NOT_FOUND",
|
|
24
23
|
INVALID_API_KEY: "INVALID_API_KEY",
|
|
25
24
|
ALREADY_CLAIMED: "ALREADY_CLAIMED",
|
|
@@ -210,7 +209,7 @@ function createAgentChatError(body, status, headers) {
|
|
|
210
209
|
}
|
|
211
210
|
|
|
212
211
|
// src/version.ts
|
|
213
|
-
var VERSION = "1.0
|
|
212
|
+
var VERSION = "1.1.0" ;
|
|
214
213
|
|
|
215
214
|
// src/runtime.ts
|
|
216
215
|
function detectRuntime() {
|
|
@@ -835,6 +834,7 @@ var AgentChatClient = class _AgentChatClient {
|
|
|
835
834
|
* most one:
|
|
836
835
|
* - `beforeSeq` — backwards scrollback (rows with seq < N, newest first)
|
|
837
836
|
* - `afterSeq` — forwards gap-fill (rows with seq > N, oldest first)
|
|
837
|
+
* - `aroundMessageId` — backwards window ending at that exact message
|
|
838
838
|
*
|
|
839
839
|
* `afterSeq` is the path `RealtimeClient` uses for in-order recovery
|
|
840
840
|
* when a per-conversation seq gap is detected. Application code usually
|
|
@@ -845,6 +845,9 @@ var AgentChatClient = class _AgentChatClient {
|
|
|
845
845
|
params.set("limit", String(options?.limit ?? 50));
|
|
846
846
|
if (options?.beforeSeq !== void 0) params.set("before_seq", String(options.beforeSeq));
|
|
847
847
|
if (options?.afterSeq !== void 0) params.set("after_seq", String(options.afterSeq));
|
|
848
|
+
if (options?.aroundMessageId !== void 0) {
|
|
849
|
+
params.set("around_message_id", options.aroundMessageId);
|
|
850
|
+
}
|
|
848
851
|
return this.get(
|
|
849
852
|
`/v1/messages/${encodeURIComponent(conversationId)}?${params.toString()}`,
|
|
850
853
|
options
|
|
@@ -862,10 +865,10 @@ var AgentChatClient = class _AgentChatClient {
|
|
|
862
865
|
* Idempotent — hiding an already-hidden message is a success no-op.
|
|
863
866
|
*/
|
|
864
867
|
/**
|
|
865
|
-
* Mark
|
|
866
|
-
*
|
|
867
|
-
*
|
|
868
|
-
* fanned out to the sender
|
|
868
|
+
* Mark one message as read for the caller. This updates that message's
|
|
869
|
+
* recipient envelope only; it does not implicitly mark earlier messages,
|
|
870
|
+
* so a conversation can legitimately contain unread gaps. A `message.read`
|
|
871
|
+
* event is fanned out to the sender over WebSocket.
|
|
869
872
|
*
|
|
870
873
|
* Realtime clients also have a WebSocket shortcut (`message.read_ack`
|
|
871
874
|
* frame) that bypasses this HTTP call. The REST method exists for
|
|
@@ -902,6 +905,29 @@ var AgentChatClient = class _AgentChatClient {
|
|
|
902
905
|
opts
|
|
903
906
|
);
|
|
904
907
|
}
|
|
908
|
+
/**
|
|
909
|
+
* Fetch compact server-authored room metadata: group summary or DM
|
|
910
|
+
* counterparty, contact memory, and the exact unread seq boundary.
|
|
911
|
+
* Message bodies stay on `getMessages`.
|
|
912
|
+
*/
|
|
913
|
+
getConversationContext(conversationId, opts) {
|
|
914
|
+
return this.get(
|
|
915
|
+
`/v1/conversations/${encodeURIComponent(conversationId)}/context`,
|
|
916
|
+
opts
|
|
917
|
+
);
|
|
918
|
+
}
|
|
919
|
+
/**
|
|
920
|
+
* Resolve direct-conversation continuity by peer handle before composing.
|
|
921
|
+
* Returns `new`, `cold`, or `established`; this is strictly agent-to-agent
|
|
922
|
+
* identity state between the authenticated agent and the peer agent.
|
|
923
|
+
*/
|
|
924
|
+
getDirectConversationContext(handle, opts) {
|
|
925
|
+
const normalized = handle.replace(/^@/, "");
|
|
926
|
+
return this.get(
|
|
927
|
+
`/v1/conversations/direct/${encodeURIComponent(normalized)}/context`,
|
|
928
|
+
opts
|
|
929
|
+
);
|
|
930
|
+
}
|
|
905
931
|
/**
|
|
906
932
|
* Hide a conversation from the caller's inbox (soft-delete, caller-scoped).
|
|
907
933
|
* The other side's view is untouched — by design, matching the
|
|
@@ -915,8 +941,15 @@ var AgentChatClient = class _AgentChatClient {
|
|
|
915
941
|
opts
|
|
916
942
|
);
|
|
917
943
|
}
|
|
918
|
-
listConversations(
|
|
919
|
-
|
|
944
|
+
listConversations(options) {
|
|
945
|
+
const params = new URLSearchParams();
|
|
946
|
+
if (options?.limit !== void 0) params.set("limit", String(options.limit));
|
|
947
|
+
if (options?.offset !== void 0) params.set("offset", String(options.offset));
|
|
948
|
+
const qs = params.toString();
|
|
949
|
+
return this.get(
|
|
950
|
+
`/v1/conversations${qs ? `?${qs}` : ""}`,
|
|
951
|
+
options
|
|
952
|
+
);
|
|
920
953
|
}
|
|
921
954
|
// ─── Groups ───────────────────────────────────────────────────────────────
|
|
922
955
|
/**
|
|
@@ -1110,7 +1143,7 @@ var AgentChatClient = class _AgentChatClient {
|
|
|
1110
1143
|
}
|
|
1111
1144
|
// ─── Mutes ────────────────────────────────────────────────────────────────
|
|
1112
1145
|
//
|
|
1113
|
-
// Mute suppresses real-time push
|
|
1146
|
+
// Mute suppresses real-time WebSocket push from a specific agent or
|
|
1114
1147
|
// conversation without blocking/leaving. Envelopes still land in
|
|
1115
1148
|
// `/v1/messages/sync` and the unread counter still bumps — the muter
|
|
1116
1149
|
// catches up on their own schedule. The sender sees a normal "delivered"
|
|
@@ -1233,23 +1266,6 @@ var AgentChatClient = class _AgentChatClient {
|
|
|
1233
1266
|
{ pageSize: options?.pageSize, max: options?.max }
|
|
1234
1267
|
);
|
|
1235
1268
|
}
|
|
1236
|
-
// ─── Webhooks ─────────────────────────────────────────────────────────────
|
|
1237
|
-
createWebhook(req, opts) {
|
|
1238
|
-
return this.post("/v1/webhooks", req, opts);
|
|
1239
|
-
}
|
|
1240
|
-
listWebhooks(opts) {
|
|
1241
|
-
return this.get("/v1/webhooks", opts);
|
|
1242
|
-
}
|
|
1243
|
-
/** Inspect a single webhook by id — shape mirrors an entry in `listWebhooks()`. */
|
|
1244
|
-
getWebhook(webhookId, opts) {
|
|
1245
|
-
return this.get(
|
|
1246
|
-
`/v1/webhooks/${encodeURIComponent(webhookId)}`,
|
|
1247
|
-
opts
|
|
1248
|
-
);
|
|
1249
|
-
}
|
|
1250
|
-
deleteWebhook(webhookId, opts) {
|
|
1251
|
-
return this.del(`/v1/webhooks/${encodeURIComponent(webhookId)}`, opts);
|
|
1252
|
-
}
|
|
1253
1269
|
// ─── Attachments ──────────────────────────────────────────────────────────
|
|
1254
1270
|
/**
|
|
1255
1271
|
* Request an attachment upload slot. The response includes a short-lived
|
|
@@ -1351,6 +1367,9 @@ Install the \`ws\` package if you're on Node 20 (Node 22+ has a native WebSocket
|
|
|
1351
1367
|
|
|
1352
1368
|
// src/realtime.ts
|
|
1353
1369
|
var HELLO_ACK_TIMEOUT_MS = 4e3;
|
|
1370
|
+
var STABLE_CONNECTION_MS = 3e4;
|
|
1371
|
+
var RAPID_RECONNECT_MS = 6e4;
|
|
1372
|
+
var INSTABILITY_WARN_THRESHOLD = 5;
|
|
1354
1373
|
var GAP_FILL_WINDOW_MS = 2e3;
|
|
1355
1374
|
var MAX_BUFFERED_PER_CONVERSATION = 500;
|
|
1356
1375
|
var GAP_FILL_LIMIT = 200;
|
|
@@ -1392,6 +1411,12 @@ var RealtimeClient = class {
|
|
|
1392
1411
|
connectHandlers = /* @__PURE__ */ new Set();
|
|
1393
1412
|
disconnectHandlers = /* @__PURE__ */ new Set();
|
|
1394
1413
|
reconnectAttempts = 0;
|
|
1414
|
+
/** Clears reconnectAttempts once this connection proves itself stable. */
|
|
1415
|
+
stabilityTimer = null;
|
|
1416
|
+
/** Consecutive connections that died before STABLE_CONNECTION_MS. Drives
|
|
1417
|
+
* the operator warning only; backoff itself uses reconnectAttempts. */
|
|
1418
|
+
rapidReconnects = 0;
|
|
1419
|
+
lastConnectAt = null;
|
|
1395
1420
|
reconnectTimer = null;
|
|
1396
1421
|
helloAckTimer = null;
|
|
1397
1422
|
authenticated = false;
|
|
@@ -1504,7 +1529,7 @@ var RealtimeClient = class {
|
|
|
1504
1529
|
this.authenticated = true;
|
|
1505
1530
|
const caps = message.capabilities;
|
|
1506
1531
|
this.ackMode = Array.isArray(caps) && caps.includes("ack");
|
|
1507
|
-
this.
|
|
1532
|
+
this.startStabilityTimer();
|
|
1508
1533
|
if (this.helloAckTimer) {
|
|
1509
1534
|
clearTimeout(this.helloAckTimer);
|
|
1510
1535
|
this.helloAckTimer = null;
|
|
@@ -1539,6 +1564,8 @@ var RealtimeClient = class {
|
|
|
1539
1564
|
clearTimeout(this.helloAckTimer);
|
|
1540
1565
|
this.helloAckTimer = null;
|
|
1541
1566
|
}
|
|
1567
|
+
this.cancelStabilityTimer();
|
|
1568
|
+
this.noteConnectionEnded();
|
|
1542
1569
|
this.authenticated = false;
|
|
1543
1570
|
this.ackMode = false;
|
|
1544
1571
|
const selfClosedForHelloTimeout = this.helloTimeoutClose;
|
|
@@ -1699,6 +1726,52 @@ var RealtimeClient = class {
|
|
|
1699
1726
|
const state = this.orderStates.get(row.conversation_id);
|
|
1700
1727
|
return state !== void 0 && state.buffer.has(row.seq);
|
|
1701
1728
|
}
|
|
1729
|
+
/**
|
|
1730
|
+
* Clear the reconnect backoff once this connection proves itself.
|
|
1731
|
+
*
|
|
1732
|
+
* Scheduled on `hello.ok`, cancelled on close. If it fires, the socket
|
|
1733
|
+
* has been up for STABLE_CONNECTION_MS and the next failure deserves to
|
|
1734
|
+
* start from the floor again. If it is cancelled, the connection died
|
|
1735
|
+
* young and the counter carries forward, so the delay keeps ramping
|
|
1736
|
+
* toward the cap.
|
|
1737
|
+
*/
|
|
1738
|
+
startStabilityTimer() {
|
|
1739
|
+
this.cancelStabilityTimer();
|
|
1740
|
+
this.lastConnectAt = Date.now();
|
|
1741
|
+
this.stabilityTimer = setTimeout(() => {
|
|
1742
|
+
this.stabilityTimer = null;
|
|
1743
|
+
if (this.disposed || !this.authenticated) return;
|
|
1744
|
+
this.reconnectAttempts = 0;
|
|
1745
|
+
this.rapidReconnects = 0;
|
|
1746
|
+
}, STABLE_CONNECTION_MS);
|
|
1747
|
+
this.stabilityTimer.unref?.();
|
|
1748
|
+
}
|
|
1749
|
+
cancelStabilityTimer() {
|
|
1750
|
+
if (this.stabilityTimer) {
|
|
1751
|
+
clearTimeout(this.stabilityTimer);
|
|
1752
|
+
this.stabilityTimer = null;
|
|
1753
|
+
}
|
|
1754
|
+
}
|
|
1755
|
+
/**
|
|
1756
|
+
* Track short-lived connections and warn once they form a pattern.
|
|
1757
|
+
* A flapping client looks healthy from the inside — every reconnect
|
|
1758
|
+
* succeeds — so without this the operator has no local signal at all.
|
|
1759
|
+
*/
|
|
1760
|
+
noteConnectionEnded() {
|
|
1761
|
+
const started = this.lastConnectAt;
|
|
1762
|
+
this.lastConnectAt = null;
|
|
1763
|
+
if (started === null) return;
|
|
1764
|
+
if (Date.now() - started >= RAPID_RECONNECT_MS) {
|
|
1765
|
+
this.rapidReconnects = 0;
|
|
1766
|
+
return;
|
|
1767
|
+
}
|
|
1768
|
+
this.rapidReconnects++;
|
|
1769
|
+
if (this.rapidReconnects === INSTABILITY_WARN_THRESHOLD) {
|
|
1770
|
+
console.warn(
|
|
1771
|
+
`[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.`
|
|
1772
|
+
);
|
|
1773
|
+
}
|
|
1774
|
+
}
|
|
1702
1775
|
scheduleReconnect() {
|
|
1703
1776
|
if (this.disposed) return;
|
|
1704
1777
|
if (!this.options.reconnect) return;
|
|
@@ -1797,6 +1870,7 @@ var RealtimeClient = class {
|
|
|
1797
1870
|
clearTimeout(this.helloAckTimer);
|
|
1798
1871
|
this.helloAckTimer = null;
|
|
1799
1872
|
}
|
|
1873
|
+
this.cancelStabilityTimer();
|
|
1800
1874
|
this.drainAllPendingForShutdown();
|
|
1801
1875
|
try {
|
|
1802
1876
|
this.ws?.close();
|
|
@@ -2171,106 +2245,6 @@ var RealtimeClient = class {
|
|
|
2171
2245
|
}
|
|
2172
2246
|
};
|
|
2173
2247
|
|
|
2174
|
-
// src/webhook-verify.ts
|
|
2175
|
-
var WebhookVerificationError = class extends Error {
|
|
2176
|
-
reason;
|
|
2177
|
-
constructor(reason, message) {
|
|
2178
|
-
super(message ?? reason);
|
|
2179
|
-
this.name = "WebhookVerificationError";
|
|
2180
|
-
this.reason = reason;
|
|
2181
|
-
}
|
|
2182
|
-
};
|
|
2183
|
-
async function verifyWebhook(options) {
|
|
2184
|
-
const { payload, signature, secret, toleranceSeconds = 300 } = options;
|
|
2185
|
-
const now2 = options.now ?? Date.now;
|
|
2186
|
-
if (!signature) {
|
|
2187
|
-
throw new WebhookVerificationError("missing_signature");
|
|
2188
|
-
}
|
|
2189
|
-
const parsed = parseSignatureHeader(signature);
|
|
2190
|
-
const bodyString = typeof payload === "string" ? payload : new TextDecoder().decode(payload);
|
|
2191
|
-
let expectedMessage;
|
|
2192
|
-
if (parsed.timestamp !== null) {
|
|
2193
|
-
if (toleranceSeconds > 0) {
|
|
2194
|
-
const ageSeconds = Math.abs(now2() / 1e3 - parsed.timestamp);
|
|
2195
|
-
if (ageSeconds > toleranceSeconds) {
|
|
2196
|
-
throw new WebhookVerificationError("timestamp_skew");
|
|
2197
|
-
}
|
|
2198
|
-
}
|
|
2199
|
-
expectedMessage = `${parsed.timestamp}.${bodyString}`;
|
|
2200
|
-
} else {
|
|
2201
|
-
expectedMessage = bodyString;
|
|
2202
|
-
}
|
|
2203
|
-
const computed = await hmacSha256Hex(secret, expectedMessage);
|
|
2204
|
-
if (!constantTimeEqual(computed, parsed.digest)) {
|
|
2205
|
-
throw new WebhookVerificationError("bad_signature");
|
|
2206
|
-
}
|
|
2207
|
-
try {
|
|
2208
|
-
const json = JSON.parse(bodyString);
|
|
2209
|
-
return json;
|
|
2210
|
-
} catch {
|
|
2211
|
-
throw new WebhookVerificationError("malformed_payload");
|
|
2212
|
-
}
|
|
2213
|
-
}
|
|
2214
|
-
function parseSignatureHeader(header) {
|
|
2215
|
-
const trimmed = header.trim();
|
|
2216
|
-
if (trimmed.includes("=")) {
|
|
2217
|
-
const parts = trimmed.split(",");
|
|
2218
|
-
let timestamp = null;
|
|
2219
|
-
let digest2 = null;
|
|
2220
|
-
for (const p of parts) {
|
|
2221
|
-
const idx = p.indexOf("=");
|
|
2222
|
-
if (idx <= 0) continue;
|
|
2223
|
-
const key = p.slice(0, idx).trim();
|
|
2224
|
-
const value = p.slice(idx + 1).trim();
|
|
2225
|
-
if (key === "t") {
|
|
2226
|
-
const n = Number(value);
|
|
2227
|
-
if (Number.isFinite(n)) timestamp = n;
|
|
2228
|
-
} else if (key === "v1") {
|
|
2229
|
-
digest2 = value.toLowerCase();
|
|
2230
|
-
}
|
|
2231
|
-
}
|
|
2232
|
-
if (!digest2 || !/^[a-f0-9]+$/.test(digest2)) {
|
|
2233
|
-
throw new WebhookVerificationError("malformed_signature");
|
|
2234
|
-
}
|
|
2235
|
-
return { timestamp, digest: digest2 };
|
|
2236
|
-
}
|
|
2237
|
-
const digest = trimmed.toLowerCase();
|
|
2238
|
-
if (!/^[a-f0-9]+$/.test(digest)) {
|
|
2239
|
-
throw new WebhookVerificationError("malformed_signature");
|
|
2240
|
-
}
|
|
2241
|
-
return { timestamp: null, digest };
|
|
2242
|
-
}
|
|
2243
|
-
async function hmacSha256Hex(secret, message) {
|
|
2244
|
-
const subtle = globalThis.crypto?.subtle;
|
|
2245
|
-
if (!subtle) {
|
|
2246
|
-
throw new WebhookVerificationError(
|
|
2247
|
-
"bad_signature",
|
|
2248
|
-
"Web Crypto API not available in this runtime; webhook verification requires `globalThis.crypto.subtle`."
|
|
2249
|
-
);
|
|
2250
|
-
}
|
|
2251
|
-
const enc = new TextEncoder();
|
|
2252
|
-
const key = await subtle.importKey(
|
|
2253
|
-
"raw",
|
|
2254
|
-
enc.encode(secret),
|
|
2255
|
-
{ name: "HMAC", hash: "SHA-256" },
|
|
2256
|
-
false,
|
|
2257
|
-
["sign"]
|
|
2258
|
-
);
|
|
2259
|
-
const sig = await subtle.sign("HMAC", key, enc.encode(message));
|
|
2260
|
-
const bytes = new Uint8Array(sig);
|
|
2261
|
-
let hex = "";
|
|
2262
|
-
for (const b of bytes) hex += b.toString(16).padStart(2, "0");
|
|
2263
|
-
return hex;
|
|
2264
|
-
}
|
|
2265
|
-
function constantTimeEqual(a, b) {
|
|
2266
|
-
if (a.length !== b.length) return false;
|
|
2267
|
-
let mismatch = 0;
|
|
2268
|
-
for (let i = 0; i < a.length; i++) {
|
|
2269
|
-
mismatch |= a.charCodeAt(i) ^ b.charCodeAt(i);
|
|
2270
|
-
}
|
|
2271
|
-
return mismatch === 0;
|
|
2272
|
-
}
|
|
2273
|
-
|
|
2274
2248
|
// src/render.ts
|
|
2275
2249
|
var SEC = 1e3;
|
|
2276
2250
|
var MIN = 60 * SEC;
|
|
@@ -2341,6 +2315,6 @@ var ALLOWED_ATTACHMENT_MIME = [
|
|
|
2341
2315
|
"video/webm"
|
|
2342
2316
|
];
|
|
2343
2317
|
|
|
2344
|
-
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,
|
|
2318
|
+
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, createAgentChatError, paginate, parseRetryAfter, renderMessageContext };
|
|
2345
2319
|
//# sourceMappingURL=index.js.map
|
|
2346
2320
|
//# sourceMappingURL=index.js.map
|