dooers-agents-client 0.9.0 → 0.9.3

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/main.cjs CHANGED
@@ -24,6 +24,9 @@ function apiMessagesUrlToWebSocketUrl(url) {
24
24
  throw new Error("API Messages URL must start with http://, https://, ws://, or wss://");
25
25
  }
26
26
 
27
+ // src/version.ts
28
+ var PACKAGE_VERSION = "0.9.3";
29
+
27
30
  // src/types.ts
28
31
  function isSettingsFieldGroup(item) {
29
32
  return "fields" in item && Array.isArray(item.fields);
@@ -34,6 +37,8 @@ function toUser(w) {
34
37
  userId: w.user_id,
35
38
  userName: w.user_name,
36
39
  userEmail: w.user_email,
40
+ userMobileNumber: w.user_mobile_number,
41
+ userWhatsappNumber: w.user_whatsapp_number,
37
42
  identityIds: w.identity_ids,
38
43
  systemRole: w.system_role,
39
44
  organizationRole: w.organization_role,
@@ -65,7 +70,8 @@ function toDisplayContentPart(w) {
65
70
  url: w.url,
66
71
  mimeType: w.mime_type,
67
72
  duration: w.duration,
68
- filename: w.filename
73
+ filename: w.filename,
74
+ ...w.ref_id ? { refId: w.ref_id } : {}
69
75
  };
70
76
  case "image":
71
77
  return {
@@ -75,7 +81,8 @@ function toDisplayContentPart(w) {
75
81
  width: w.width,
76
82
  height: w.height,
77
83
  alt: w.alt,
78
- filename: w.filename
84
+ filename: w.filename,
85
+ ...w.ref_id ? { refId: w.ref_id } : {}
79
86
  };
80
87
  case "document":
81
88
  return {
@@ -83,7 +90,8 @@ function toDisplayContentPart(w) {
83
90
  url: w.url,
84
91
  filename: w.filename,
85
92
  mimeType: w.mime_type,
86
- sizeBytes: w.size_bytes
93
+ sizeBytes: w.size_bytes,
94
+ ...w.ref_id ? { refId: w.ref_id } : {}
87
95
  };
88
96
  }
89
97
  }
@@ -246,6 +254,8 @@ function toWireContentPart(p) {
246
254
  ref_id: p.refId
247
255
  };
248
256
  if (p.duration != null) w.duration = p.duration;
257
+ if (p.filename?.trim()) w.filename = p.filename.trim();
258
+ if (p.mimeType?.trim()) w.mime_type = p.mimeType.trim();
249
259
  if (p.url) w.url = p.url;
250
260
  return w;
251
261
  }
@@ -254,6 +264,8 @@ function toWireContentPart(p) {
254
264
  type: "image",
255
265
  ref_id: p.refId
256
266
  };
267
+ if (p.filename?.trim()) w.filename = p.filename.trim();
268
+ if (p.mimeType?.trim()) w.mime_type = p.mimeType.trim();
257
269
  if (p.url) w.url = p.url;
258
270
  return w;
259
271
  }
@@ -262,6 +274,8 @@ function toWireContentPart(p) {
262
274
  type: "document",
263
275
  ref_id: p.refId
264
276
  };
277
+ if (p.filename?.trim()) w.filename = p.filename.trim();
278
+ if (p.mimeType?.trim()) w.mime_type = p.mimeType.trim();
265
279
  if (p.url) w.url = p.url;
266
280
  return w;
267
281
  }
@@ -272,6 +286,35 @@ function toWireContentPart(p) {
272
286
  var MAX_RECONNECT_ATTEMPTS = 5;
273
287
  var RECONNECT_DELAYS = [1e3, 2e3, 4e3, 8e3, 16e3];
274
288
  var SEND_MESSAGE_TIMEOUT = 3e4;
289
+ function blobPartFilename(blob, override) {
290
+ const o = (override ?? "").trim();
291
+ if (o) return o;
292
+ const t = (blob.type || "").toLowerCase();
293
+ if (t.includes("webm")) return "recording.webm";
294
+ if (t.includes("ogg")) return "recording.ogg";
295
+ if (t.includes("mpeg") || t.includes("mp3")) return "recording.mp3";
296
+ if (t.includes("mp4") || t.includes("m4a") || t.includes("aac")) return "recording.m4a";
297
+ if (t.startsWith("audio/")) return `recording.${t.split("/")[1]?.split("+")[0] || "bin"}`;
298
+ return "recording.bin";
299
+ }
300
+ async function readHttpErrorMessage(response) {
301
+ const raw = await response.text().catch(() => "") || "";
302
+ const trimmed = raw.trim();
303
+ if (!trimmed) {
304
+ return `Upload failed: ${response.status} ${response.statusText}`.trim();
305
+ }
306
+ try {
307
+ const j = JSON.parse(trimmed);
308
+ const d = j.detail;
309
+ if (typeof d === "string") return d;
310
+ if (Array.isArray(d) && d.length > 0) {
311
+ const first = d[0];
312
+ if (first && typeof first.msg === "string") return first.msg;
313
+ }
314
+ } catch {
315
+ }
316
+ return trimmed.length > 300 ? `${trimmed.slice(0, 300)}\u2026` : trimmed;
317
+ }
275
318
  var AgentServerClient = class {
276
319
  wsUrl;
277
320
  constructor(apiMessagesUrl) {
@@ -365,11 +408,18 @@ var AgentClient = class {
365
408
  url = "";
366
409
  httpBaseUrl = "";
367
410
  agentId = "";
411
+ /** Mirrors ``AgentProvider`` ``agentId`` so HTTP uploads work before WS ``connect`` completes. */
412
+ uploadAgentId = "";
413
+ /** Current chat thread id for ``POST /uploads`` (aligns GCS path with ``hydrate_thread_events``). */
414
+ uploadThreadId = "";
415
+ /** Optional run id for ``POST /uploads`` (metadata only). */
416
+ uploadRunId = "";
368
417
  uploadUrl;
369
418
  config = {
370
419
  organizationId: "",
371
420
  workspaceId: "",
372
- userId: ""
421
+ userId: "",
422
+ channel: "dooers-platform"
373
423
  };
374
424
  connectFrameId = "";
375
425
  isIntentionallyClosed = false;
@@ -379,7 +429,7 @@ var AgentClient = class {
379
429
  subscriptionRefs = /* @__PURE__ */ new Map();
380
430
  // Track optimistic events for reconciliation
381
431
  pendingOptimistic = /* @__PURE__ */ new Map();
382
- // Pending message promises — resolved when event.append arrives with matching clientEventId
432
+ // Pending message promises — keyed by outbound frame id (same as client_event_id for sends).
383
433
  pendingMessages = /* @__PURE__ */ new Map();
384
434
  // Track last event ID per thread for gap recovery on reconnect
385
435
  lastEventIds = /* @__PURE__ */ new Map();
@@ -397,14 +447,40 @@ var AgentClient = class {
397
447
  setUploadUrl(url) {
398
448
  this.uploadUrl = url;
399
449
  }
400
- async upload(file) {
450
+ /** Sync from ``AgentProvider`` ``agentId``; used for ``POST /uploads`` ``agent_id`` (non-WS). */
451
+ setUploadAgentId(agentId) {
452
+ this.uploadAgentId = (agentId ?? "").trim();
453
+ }
454
+ /** Sync active thread id for chat uploads (must match persisted thread for signed URL keys). */
455
+ setUploadThreadId(threadId) {
456
+ this.uploadThreadId = (threadId ?? "").trim();
457
+ }
458
+ setUploadRunId(runId) {
459
+ this.uploadRunId = (runId ?? "").trim();
460
+ }
461
+ async upload(file, options) {
401
462
  if (!this.uploadUrl) {
402
463
  throw new Error("uploadUrl not configured");
403
464
  }
465
+ const aid = (this.uploadAgentId || this.agentId || "").trim();
466
+ if (!aid) {
467
+ throw new Error("agentId is required for upload (set AgentProvider agentId)");
468
+ }
404
469
  const formData = new FormData();
405
- formData.append("file", file);
406
- if (this.agentId) {
407
- formData.append("agent_id", this.agentId);
470
+ if (file instanceof File) {
471
+ const partName = options?.filename?.trim() || file.name.trim() || "upload";
472
+ formData.append("file", file, partName);
473
+ } else {
474
+ formData.append("file", file, blobPartFilename(file, options?.filename));
475
+ }
476
+ formData.append("agent_id", aid);
477
+ const tid = this.uploadThreadId.trim();
478
+ if (tid) {
479
+ formData.append("thread_id", tid);
480
+ }
481
+ const rid = this.uploadRunId.trim();
482
+ if (rid) {
483
+ formData.append("run_id", rid);
408
484
  }
409
485
  const headers = {};
410
486
  if (this.config.authToken) {
@@ -416,7 +492,8 @@ var AgentClient = class {
416
492
  headers
417
493
  });
418
494
  if (!response.ok) {
419
- throw new Error(`Upload failed: ${response.status} ${response.statusText}`);
495
+ const detail = await readHttpErrorMessage(response);
496
+ throw new Error(detail);
420
497
  }
421
498
  const result = await response.json();
422
499
  return {
@@ -430,6 +507,7 @@ var AgentClient = class {
430
507
  connect(url, agentId, config) {
431
508
  this.url = apiMessagesUrlToWebSocketUrl(url);
432
509
  this.agentId = agentId;
510
+ this.uploadAgentId = agentId.trim();
433
511
  this.config = config ?? { organizationId: "", workspaceId: "", userId: "" };
434
512
  this.isIntentionallyClosed = false;
435
513
  try {
@@ -445,16 +523,29 @@ var AgentClient = class {
445
523
  this.callbacks.setConnectionStatus("connecting");
446
524
  this.createConnection();
447
525
  }
526
+ /** Clear the last in-chat send error banner (see ``connection.sendError``). */
527
+ clearSendError() {
528
+ this.callbacks.setSendError(null);
529
+ }
530
+ abortPendingMessages(reason) {
531
+ for (const [clientEventId, pending] of this.pendingMessages) {
532
+ clearTimeout(pending.timer);
533
+ const opt = this.pendingOptimistic.get(clientEventId);
534
+ if (opt?.threadId) {
535
+ this.callbacks.removeOptimistic(opt.threadId, clientEventId);
536
+ }
537
+ this.pendingOptimistic.delete(clientEventId);
538
+ pending.reject(new Error(reason));
539
+ }
540
+ this.pendingMessages.clear();
541
+ }
448
542
  disconnect() {
449
543
  this.isIntentionallyClosed = true;
450
544
  if (this.reconnectTimer) {
451
545
  clearTimeout(this.reconnectTimer);
452
546
  this.reconnectTimer = null;
453
547
  }
454
- for (const [, pending] of this.pendingMessages) {
455
- clearTimeout(pending.timer);
456
- }
457
- this.pendingMessages.clear();
548
+ this.abortPendingMessages("Disconnected");
458
549
  if (this.ws) {
459
550
  this.ws.onopen = null;
460
551
  this.ws.onmessage = null;
@@ -546,6 +637,7 @@ var AgentClient = class {
546
637
  }
547
638
  // --- Messaging ---
548
639
  sendMessage(params) {
640
+ this.callbacks.setSendError(null);
549
641
  const clientEventId = crypto.randomUUID();
550
642
  const content = params.content ? params.content.map(toWireContentPart) : [{ type: "text", text: params.text ?? "" }];
551
643
  const displayContent = params.content ? params.content.map((p) => {
@@ -553,11 +645,27 @@ var AgentClient = class {
553
645
  case "text":
554
646
  return { type: "text", text: p.text };
555
647
  case "audio":
556
- return { type: "audio", duration: p.duration };
648
+ return {
649
+ type: "audio",
650
+ duration: p.duration,
651
+ ...p.url ? { url: p.url } : {},
652
+ ...p.mimeType ? { mimeType: p.mimeType } : {},
653
+ ...p.filename ? { filename: p.filename } : {}
654
+ };
557
655
  case "image":
558
- return { type: "image" };
656
+ return {
657
+ type: "image",
658
+ ...p.url ? { url: p.url } : {},
659
+ ...p.mimeType ? { mimeType: p.mimeType } : {},
660
+ ...p.filename ? { filename: p.filename } : {}
661
+ };
559
662
  case "document":
560
- return { type: "document" };
663
+ return {
664
+ type: "document",
665
+ ...p.url ? { url: p.url } : {},
666
+ ...p.mimeType ? { mimeType: p.mimeType } : {},
667
+ ...p.filename ? { filename: p.filename } : {}
668
+ };
561
669
  default:
562
670
  return { type: "text", text: "" };
563
671
  }
@@ -598,28 +706,36 @@ var AgentClient = class {
598
706
  content
599
707
  }
600
708
  };
601
- if (params.metadata) {
602
- payload.metadata = params.metadata;
603
- }
604
- this.send("event.create", payload);
709
+ payload.metadata = this.withChannelMetadata(params.metadata);
710
+ this.sendRaw({
711
+ id: clientEventId,
712
+ type: "event.create",
713
+ payload
714
+ });
605
715
  return promise;
606
716
  }
607
717
  sendFormResponse(params) {
718
+ this.callbacks.setSendError(null);
608
719
  const clientEventId = crypto.randomUUID();
609
720
  const promise = this.createPendingPromise(clientEventId);
610
- this.send("event.create", {
611
- thread_id: params.threadId,
612
- client_event_id: clientEventId,
613
- event: {
614
- type: "form.response",
615
- actor: "user",
616
- content: [],
617
- data: {
618
- form_event_id: params.formEventId,
619
- cancelled: params.cancelled,
620
- values: params.values
721
+ this.sendRaw({
722
+ id: clientEventId,
723
+ type: "event.create",
724
+ payload: {
725
+ thread_id: params.threadId,
726
+ client_event_id: clientEventId,
727
+ event: {
728
+ type: "form.response",
729
+ actor: "user",
730
+ content: [],
731
+ data: {
732
+ form_event_id: params.formEventId,
733
+ cancelled: params.cancelled,
734
+ values: params.values
735
+ }
621
736
  }
622
- }
737
+ },
738
+ metadata: this.withChannelMetadata(params.metadata)
623
739
  });
624
740
  return promise;
625
741
  }
@@ -627,14 +743,20 @@ var AgentClient = class {
627
743
  createPendingPromise(clientEventId) {
628
744
  return new Promise((resolve, reject) => {
629
745
  const timer = setTimeout(() => {
746
+ const opt = this.pendingOptimistic.get(clientEventId);
747
+ if (opt?.threadId) {
748
+ this.callbacks.removeOptimistic(opt.threadId, clientEventId);
749
+ }
750
+ this.pendingOptimistic.delete(clientEventId);
630
751
  this.pendingMessages.delete(clientEventId);
631
752
  reject(new Error("Request timed out waiting for server response"));
632
753
  }, SEND_MESSAGE_TIMEOUT);
633
- this.pendingMessages.set(clientEventId, { resolve, timer });
754
+ this.pendingMessages.set(clientEventId, { resolve, reject, timer });
634
755
  });
635
756
  }
636
757
  createConnection() {
637
758
  if (this.ws) {
759
+ this.abortPendingMessages("Connection lost; reconnecting");
638
760
  this.ws.onopen = null;
639
761
  this.ws.onmessage = null;
640
762
  this.ws.onclose = null;
@@ -673,13 +795,15 @@ var AgentClient = class {
673
795
  user_id: this.config.userId ?? "",
674
796
  user_name: this.config.userName ?? null,
675
797
  user_email: this.config.userEmail ?? null,
798
+ user_mobile_number: this.config.userMobileNumber ?? null,
799
+ user_whatsapp_number: this.config.userWhatsappNumber ?? null,
676
800
  identity_ids: this.config.identityIds ?? [],
677
801
  system_role: this.config.systemRole ?? "user",
678
802
  organization_role: this.config.organizationRole ?? "member",
679
803
  workspace_role: this.config.workspaceRole ?? "member"
680
804
  },
681
805
  auth_token: this.config.authToken,
682
- client: { name: "dooers-agents-client", version: "0.1.0" }
806
+ client: { name: "dooers-agents-client", version: PACKAGE_VERSION }
683
807
  }
684
808
  });
685
809
  }
@@ -702,9 +826,26 @@ var AgentClient = class {
702
826
  this.callbacks.setConnectionStatus("error", frame.payload.error?.message);
703
827
  }
704
828
  } else if (!frame.payload.ok && frame.payload.error) {
829
+ const ackId = frame.payload.ack_id;
830
+ const err = frame.payload.error;
831
+ if (ackId) {
832
+ const pending = this.pendingMessages.get(ackId);
833
+ if (pending) {
834
+ clearTimeout(pending.timer);
835
+ this.pendingMessages.delete(ackId);
836
+ const msg = err.message || "Request rejected";
837
+ const opt = this.pendingOptimistic.get(ackId);
838
+ if (opt?.threadId) {
839
+ this.callbacks.removeOptimistic(opt.threadId, ackId);
840
+ }
841
+ this.pendingOptimistic.delete(ackId);
842
+ pending.reject(new Error(msg));
843
+ this.callbacks.setSendError(msg);
844
+ }
845
+ }
705
846
  this.onError?.({
706
- code: frame.payload.error.code,
707
- message: frame.payload.error.message,
847
+ code: err.code,
848
+ message: err.message,
708
849
  frameType: "ack"
709
850
  });
710
851
  }
@@ -741,6 +882,7 @@ var AgentClient = class {
741
882
  case "event.append": {
742
883
  const events = this.resolveEventUrls(frame.payload.events.map(toThreadEvent));
743
884
  const resolvedClientEventIds = [];
885
+ const pendingIdsToResolve = [];
744
886
  for (const event of events) {
745
887
  if (event.clientEventId && this.pendingOptimistic.has(event.clientEventId)) {
746
888
  resolvedClientEventIds.push(event.clientEventId);
@@ -750,8 +892,7 @@ var AgentClient = class {
750
892
  const pendingMessage = this.pendingMessages.get(event.clientEventId);
751
893
  if (pendingMessage) {
752
894
  clearTimeout(pendingMessage.timer);
753
- pendingMessage.resolve({ threadId: frame.payload.thread_id });
754
- this.pendingMessages.delete(event.clientEventId);
895
+ pendingIdsToResolve.push(event.clientEventId);
755
896
  }
756
897
  }
757
898
  }
@@ -764,6 +905,14 @@ var AgentClient = class {
764
905
  } else {
765
906
  this.callbacks.onEventAppend(frame.payload.thread_id, events);
766
907
  }
908
+ const tid = frame.payload.thread_id;
909
+ for (const id of pendingIdsToResolve) {
910
+ const pending = this.pendingMessages.get(id);
911
+ if (pending) {
912
+ pending.resolve({ threadId: tid });
913
+ this.pendingMessages.delete(id);
914
+ }
915
+ }
767
916
  break;
768
917
  }
769
918
  case "event.list.result": {
@@ -816,6 +965,7 @@ var AgentClient = class {
816
965
  }
817
966
  scheduleReconnect() {
818
967
  if (this.reconnectAttempts >= MAX_RECONNECT_ATTEMPTS) {
968
+ this.abortPendingMessages("Connection lost after maximum retries");
819
969
  this.callbacks.setReconnectFailed();
820
970
  this.callbacks.setConnectionStatus("error", "Connection lost after maximum retries");
821
971
  return;
@@ -831,6 +981,17 @@ var AgentClient = class {
831
981
  send(type, payload) {
832
982
  this.sendRaw({ id: crypto.randomUUID(), type, payload });
833
983
  }
984
+ withChannelMetadata(metadata) {
985
+ const channel = (this.config.channel || "dooers-platform").trim() || "dooers-platform";
986
+ const base = metadata ? { ...metadata } : {};
987
+ if (!("channel" in base)) {
988
+ base.channel = channel;
989
+ }
990
+ if (this.config.channelMeta && !("channel_meta" in base)) {
991
+ base.channel_meta = this.config.channelMeta;
992
+ }
993
+ return Object.keys(base).length > 0 ? base : void 0;
994
+ }
834
995
  sendRaw(frame) {
835
996
  if (!this.ws || this.ws.readyState !== WebSocket.OPEN) return;
836
997
  this.ws.send(JSON.stringify(frame));
@@ -850,6 +1011,43 @@ var AgentClient = class {
850
1011
  });
851
1012
  }
852
1013
  };
1014
+ function mergeDisplayContentParts(prev, next) {
1015
+ if (!next?.length) return prev;
1016
+ if (!prev?.length) return next;
1017
+ const out = [];
1018
+ const len = Math.max(prev.length, next.length);
1019
+ for (let i = 0; i < len; i++) {
1020
+ const p = prev[i];
1021
+ const n = next[i];
1022
+ if (!n) {
1023
+ if (p) out.push(p);
1024
+ continue;
1025
+ }
1026
+ if (!p) {
1027
+ out.push(n);
1028
+ continue;
1029
+ }
1030
+ if (p.type !== n.type) {
1031
+ out.push(n);
1032
+ continue;
1033
+ }
1034
+ if (n.type === "image" || n.type === "audio" || n.type === "document") {
1035
+ const url = n.url ?? ("url" in p ? p.url : void 0);
1036
+ out.push({ ...p, ...n, ...url ? { url } : {} });
1037
+ continue;
1038
+ }
1039
+ out.push(n);
1040
+ }
1041
+ return out;
1042
+ }
1043
+ function mergeThreadEventById(prev, next) {
1044
+ const content = mergeDisplayContentParts(prev.content, next.content);
1045
+ return {
1046
+ ...prev,
1047
+ ...next,
1048
+ ...content !== void 0 ? { content } : {}
1049
+ };
1050
+ }
853
1051
  function extractFormStates(events, existing) {
854
1052
  let result = existing;
855
1053
  for (const e of events) {
@@ -872,6 +1070,7 @@ function createAgentStore() {
872
1070
  connection: {
873
1071
  status: "idle",
874
1072
  error: null,
1073
+ sendError: null,
875
1074
  reconnectAttempts: 0,
876
1075
  reconnectFailed: false
877
1076
  },
@@ -900,7 +1099,15 @@ function createAgentStore() {
900
1099
  loadingThreads: /* @__PURE__ */ new Set(),
901
1100
  actions: {
902
1101
  setConnectionStatus: (status, error) => set((s) => ({
903
- connection: { ...s.connection, status, error: error ?? null }
1102
+ connection: {
1103
+ ...s.connection,
1104
+ status,
1105
+ error: error ?? null,
1106
+ ...status === "connected" ? { sendError: null } : {}
1107
+ }
1108
+ })),
1109
+ setSendError: (message) => set((s) => ({
1110
+ connection: { ...s.connection, sendError: message }
904
1111
  })),
905
1112
  setReconnectFailed: () => set((s) => ({
906
1113
  connection: { ...s.connection, reconnectFailed: true }
@@ -988,8 +1195,13 @@ function createAgentStore() {
988
1195
  mergedEvents = snapshotEvents;
989
1196
  } else {
990
1197
  const existingIds = new Set(existing.map((e) => e.id));
1198
+ const snapById = new Map(snapshotEvents.map((e) => [e.id, e]));
1199
+ const mergedExisting = existing.map((e) => {
1200
+ const snap = snapById.get(e.id);
1201
+ return snap ? mergeThreadEventById(e, snap) : e;
1202
+ });
991
1203
  const newFromSnapshot = snapshotEvents.filter((e) => !existingIds.has(e.id));
992
- mergedEvents = newFromSnapshot.length > 0 ? [...existing, ...newFromSnapshot] : existing;
1204
+ mergedEvents = newFromSnapshot.length > 0 ? [...mergedExisting, ...newFromSnapshot] : mergedExisting;
993
1205
  }
994
1206
  const optimistic = { ...s.optimistic };
995
1207
  const optimisticKeys = { ...s.optimisticKeys };
@@ -1013,7 +1225,8 @@ function createAgentStore() {
1013
1225
  const appends = [];
1014
1226
  for (const e of newEvents) {
1015
1227
  if (existingIds.has(e.id)) {
1016
- updates.set(e.id, e);
1228
+ const prev = existing.find((x) => x.id === e.id);
1229
+ updates.set(e.id, mergeThreadEventById(prev, e));
1017
1230
  } else {
1018
1231
  appends.push(e);
1019
1232
  }
@@ -1027,6 +1240,10 @@ function createAgentStore() {
1027
1240
  reconcileEvents: (threadId, newEvents, resolvedClientEventIds) => set((s) => {
1028
1241
  const existing = s.events[threadId] ?? [];
1029
1242
  const existingIds = new Set(existing.map((e) => e.id));
1243
+ const mergedExisting = newEvents.length > 0 ? existing.map((e) => {
1244
+ const up = newEvents.find((n) => n.id === e.id);
1245
+ return up ? mergeThreadEventById(e, up) : e;
1246
+ }) : existing;
1030
1247
  const unique = newEvents.filter((e) => !existingIds.has(e.id));
1031
1248
  let optEvents = s.optimistic[threadId] ?? [];
1032
1249
  let optKeys = s.optimisticKeys[threadId] ?? [];
@@ -1040,7 +1257,7 @@ function createAgentStore() {
1040
1257
  }
1041
1258
  }
1042
1259
  return {
1043
- events: { ...s.events, [threadId]: [...existing, ...unique] },
1260
+ events: { ...s.events, [threadId]: [...mergedExisting, ...unique] },
1044
1261
  optimistic: { ...s.optimistic, [threadId]: optEvents },
1045
1262
  optimisticKeys: { ...s.optimisticKeys, [threadId]: optKeys }
1046
1263
  };
@@ -1090,7 +1307,10 @@ function createAgentStore() {
1090
1307
  const subscriptions = new Set(s.subscriptions);
1091
1308
  subscriptions.add(threadId);
1092
1309
  const loadingThreads = new Set(s.loadingThreads);
1093
- loadingThreads.add(threadId);
1310
+ const alreadyHadEvents = (s.events[threadId]?.length ?? 0) > 0;
1311
+ if (!alreadyHadEvents) {
1312
+ loadingThreads.add(threadId);
1313
+ }
1094
1314
  return { subscriptions, loadingThreads };
1095
1315
  }),
1096
1316
  removeSubscription: (threadId) => set((s) => {
@@ -1213,11 +1433,15 @@ function AgentProvider({
1213
1433
  userId,
1214
1434
  userName,
1215
1435
  userEmail,
1436
+ userMobileNumber,
1437
+ userWhatsappNumber,
1216
1438
  identityIds,
1217
1439
  systemRole,
1218
1440
  organizationRole,
1219
1441
  workspaceRole,
1220
1442
  authToken,
1443
+ channel,
1444
+ channelMeta,
1221
1445
  uploadUrl,
1222
1446
  onError,
1223
1447
  children
@@ -1234,6 +1458,9 @@ function AgentProvider({
1234
1458
  react.useEffect(() => {
1235
1459
  clientRef.current?.setUploadUrl(uploadUrl);
1236
1460
  }, [uploadUrl]);
1461
+ react.useEffect(() => {
1462
+ clientRef.current?.setUploadAgentId(agentId);
1463
+ }, [agentId]);
1237
1464
  const identityIdsKey = identityIds?.join(",") ?? "";
1238
1465
  react.useEffect(() => {
1239
1466
  if (!url || !agentId) return;
@@ -1243,11 +1470,15 @@ function AgentProvider({
1243
1470
  userId,
1244
1471
  userName,
1245
1472
  userEmail,
1473
+ userMobileNumber,
1474
+ userWhatsappNumber,
1246
1475
  identityIds: identityIdsKey ? identityIdsKey.split(",") : void 0,
1247
1476
  systemRole,
1248
1477
  organizationRole,
1249
1478
  workspaceRole,
1250
- authToken
1479
+ authToken,
1480
+ channel,
1481
+ channelMeta
1251
1482
  });
1252
1483
  return () => clientRef.current?.disconnect();
1253
1484
  }, [
@@ -1258,11 +1489,15 @@ function AgentProvider({
1258
1489
  userId,
1259
1490
  userName,
1260
1491
  userEmail,
1492
+ userMobileNumber,
1493
+ userWhatsappNumber,
1261
1494
  identityIdsKey,
1262
1495
  systemRole,
1263
1496
  organizationRole,
1264
1497
  workspaceRole,
1265
- authToken
1498
+ authToken,
1499
+ channel,
1500
+ channelMeta
1266
1501
  ]);
1267
1502
  const contextValue = react.useMemo(
1268
1503
  () => ({ store: storeRef.current, client: clientRef.current }),
@@ -1403,9 +1638,13 @@ function useConnection() {
1403
1638
  const { client } = useAgentContext();
1404
1639
  const status = useStore((s) => s.connection.status);
1405
1640
  const error = useStore((s) => s.connection.error);
1641
+ const sendError = useStore((s) => s.connection.sendError);
1406
1642
  const reconnectFailed = useStore((s) => s.connection.reconnectFailed);
1407
1643
  const reconnect = react.useCallback(() => client.retry(), [client]);
1408
- return { status, error, reconnectFailed, reconnect };
1644
+ const dismissSendError = react.useCallback(() => {
1645
+ client.clearSendError();
1646
+ }, [client]);
1647
+ return { status, error, sendError, dismissSendError, reconnectFailed, reconnect };
1409
1648
  }
1410
1649
  function useFeedback(targetId, targetType = "event") {
1411
1650
  const { client } = useAgentContext();
@@ -1534,11 +1773,15 @@ function useFormFileUpload() {
1534
1773
  setIsUploading(true);
1535
1774
  setError(null);
1536
1775
  try {
1776
+ const aid = (params.agentId || "").trim();
1777
+ if (!aid) {
1778
+ throw new Error("agentId is required for upload");
1779
+ }
1537
1780
  const formData = new FormData();
1538
1781
  formData.append("file", params.file);
1539
1782
  formData.append("field_id", params.fieldId);
1540
1783
  formData.append("source", "chat");
1541
- formData.append("agent_id", params.agentId);
1784
+ formData.append("agent_id", aid);
1542
1785
  formData.append("run_id", params.runId);
1543
1786
  formData.append("thread_id", params.threadId);
1544
1787
  const response = await fetch(params.uploadUrl, {
@@ -1690,11 +1933,11 @@ function useUpload() {
1690
1933
  const [isUploading, setIsUploading] = react.useState(false);
1691
1934
  const activeCountRef = react.useRef(0);
1692
1935
  const upload = react.useCallback(
1693
- async (file) => {
1936
+ async (file, options) => {
1694
1937
  activeCountRef.current++;
1695
1938
  setIsUploading(true);
1696
1939
  try {
1697
- return await client.upload(file);
1940
+ return await client.upload(file, options);
1698
1941
  } finally {
1699
1942
  activeCountRef.current--;
1700
1943
  if (activeCountRef.current === 0) {
@@ -1709,11 +1952,13 @@ function useUpload() {
1709
1952
 
1710
1953
  exports.AgentProvider = AgentProvider;
1711
1954
  exports.AgentServerClient = AgentServerClient;
1955
+ exports.PACKAGE_VERSION = PACKAGE_VERSION;
1712
1956
  exports.apiMessagesUrlToWebSocketUrl = apiMessagesUrlToWebSocketUrl;
1713
1957
  exports.isSettingsFieldGroup = isSettingsFieldGroup;
1714
1958
  exports.toFormElement = toFormElement;
1715
1959
  exports.toFormEventData = toFormEventData;
1716
1960
  exports.toFormResponseEventData = toFormResponseEventData;
1961
+ exports.useAgentContext = useAgentContext;
1717
1962
  exports.useAnalytics = useAnalytics;
1718
1963
  exports.useAudioRecorder = useAudioRecorder;
1719
1964
  exports.useConnection = useConnection;