@elqnt/chat 1.0.8 → 1.0.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs CHANGED
@@ -324,6 +324,9 @@ var ChatEventTypeNewChat = "new_chat";
324
324
  var ChatEventTypeNewChatCreated = "new_chat_created";
325
325
  var ChatEventTypePing = "ping";
326
326
  var ChatEventTypePong = "pong";
327
+ var ChatEventTypeSkillActivate = "skill_activate";
328
+ var ChatEventTypeSkillDeactivate = "skill_deactivate";
329
+ var ChatEventTypeSkillsChanged = "skills_changed";
327
330
  var MessageStatusSending = "sending";
328
331
  var MessageStatusSent = "sent";
329
332
  var MessageStatusDelivered = "delivered";
@@ -411,6 +414,7 @@ var DEFAULT_QUEUE_CONFIG = {
411
414
  };
412
415
  var DEFAULT_HEARTBEAT_INTERVAL = 3e4;
413
416
  var DEFAULT_HEARTBEAT_TIMEOUT = 5e3;
417
+ var DEFAULT_TRANSPORT = "websocket";
414
418
  function isChatEvent(data) {
415
419
  return data && typeof data === "object" && (typeof data.type === "string" || data.message);
416
420
  }
@@ -425,7 +429,8 @@ var useWebSocketChatBase = ({
425
429
  debug = false,
426
430
  logger = createDefaultLogger(debug),
427
431
  heartbeatInterval = DEFAULT_HEARTBEAT_INTERVAL,
428
- heartbeatTimeout = DEFAULT_HEARTBEAT_TIMEOUT
432
+ heartbeatTimeout = DEFAULT_HEARTBEAT_TIMEOUT,
433
+ transport = DEFAULT_TRANSPORT
429
434
  }) => {
430
435
  const [connectionState, setConnectionState] = useState("disconnected");
431
436
  const [error, setError] = useState(void 0);
@@ -435,9 +440,15 @@ var useWebSocketChatBase = ({
435
440
  messagesSent: 0,
436
441
  messagesReceived: 0,
437
442
  messagesQueued: 0,
438
- reconnectCount: 0
443
+ reconnectCount: 0,
444
+ transportType: transport
439
445
  });
440
446
  const wsRef = useRef(void 0);
447
+ const sseRef = useRef(void 0);
448
+ const transportRef = useRef(transport);
449
+ useEffect(() => {
450
+ transportRef.current = transport;
451
+ }, [transport]);
441
452
  const reconnectTimeoutRef = useRef(void 0);
442
453
  const retryCountRef = useRef(0);
443
454
  const messageQueueRef = useRef([]);
@@ -567,6 +578,10 @@ var useWebSocketChatBase = ({
567
578
  wsRef.current.close(1e3, "Cleanup");
568
579
  }
569
580
  wsRef.current = void 0;
581
+ if (sseRef.current) {
582
+ sseRef.current.close();
583
+ sseRef.current = void 0;
584
+ }
570
585
  if (reconnectTimeoutRef.current) {
571
586
  clearTimeout(reconnectTimeoutRef.current);
572
587
  reconnectTimeoutRef.current = void 0;
@@ -584,6 +599,35 @@ var useWebSocketChatBase = ({
584
599
  });
585
600
  loadChatRetryMapRef.current.clear();
586
601
  }, [stopHeartbeat]);
602
+ const getRestApiUrl = useCallback((endpoint) => {
603
+ const httpUrl = serverBaseUrl.replace(/^wss:/, "https:").replace(/^ws:/, "http:");
604
+ return `${httpUrl}/${endpoint}`;
605
+ }, [serverBaseUrl]);
606
+ const sendRestMessage = useCallback(
607
+ async (endpoint, body) => {
608
+ const url = getRestApiUrl(endpoint);
609
+ logger.debug(`SSE REST API call: POST ${endpoint}`, body);
610
+ try {
611
+ const response = await fetch(url, {
612
+ method: "POST",
613
+ headers: {
614
+ "Content-Type": "application/json"
615
+ },
616
+ body: JSON.stringify(body)
617
+ });
618
+ if (!response.ok) {
619
+ const errorText = await response.text();
620
+ throw new Error(`REST API error: ${response.status} - ${errorText}`);
621
+ }
622
+ const data = await response.json();
623
+ return data;
624
+ } catch (error2) {
625
+ logger.error(`SSE REST API error for ${endpoint}:`, error2);
626
+ throw error2;
627
+ }
628
+ },
629
+ [getRestApiUrl, logger]
630
+ );
587
631
  const connect = useCallback(
588
632
  async (userId) => {
589
633
  if (!mountedRef.current) {
@@ -601,7 +645,11 @@ var useWebSocketChatBase = ({
601
645
  return Promise.reject(error2);
602
646
  }
603
647
  if (wsRef.current?.readyState === WebSocket.OPEN) {
604
- logger.debug("Already connected");
648
+ logger.debug("Already connected (WebSocket)");
649
+ return Promise.resolve();
650
+ }
651
+ if (sseRef.current?.readyState === EventSource.OPEN) {
652
+ logger.debug("Already connected (SSE)");
605
653
  return Promise.resolve();
606
654
  }
607
655
  if (connectionState === "connecting" || connectionState === "reconnecting") {
@@ -627,8 +675,169 @@ var useWebSocketChatBase = ({
627
675
  intentionalDisconnectRef.current = false;
628
676
  return new Promise((resolve, reject) => {
629
677
  try {
630
- const wsUrl = `${serverBaseUrl}?orgId=${orgId}&userId=${userId}&clientType=${clientType}&product=${product}`;
631
678
  const connectionStartTime = Date.now();
679
+ logger.info(`\u{1F504} Connecting with transport: ${transportRef.current}`);
680
+ if (transportRef.current === "sse") {
681
+ const sseUrl = getRestApiUrl(`stream?orgId=${orgId}&userId=${userId}&clientType=${clientType}&chatId=${currentChatKeyRef.current || ""}`);
682
+ logger.debug("Connecting to SSE:", sseUrl);
683
+ console.log(`\u23F3 Initiating SSE connection to ${serverBaseUrl}...`);
684
+ const eventSource = new EventSource(sseUrl);
685
+ eventSource.onopen = () => {
686
+ if (!mountedRef.current) {
687
+ eventSource.close();
688
+ reject(new Error("Component unmounted"));
689
+ return;
690
+ }
691
+ const connectionTimeMs = Date.now() - connectionStartTime;
692
+ const connectionTimeSec = (connectionTimeMs / 1e3).toFixed(2);
693
+ logger.info("\u2705 SSE connected", {
694
+ userId,
695
+ retryCount: retryCountRef.current,
696
+ connectionTime: `${connectionTimeSec}s (${connectionTimeMs}ms)`
697
+ });
698
+ console.log(`\u{1F50C} SSE connection established in ${connectionTimeSec} seconds`);
699
+ setConnectionState("connected");
700
+ setError(void 0);
701
+ const wasReconnecting = retryCountRef.current > 0;
702
+ retryCountRef.current = 0;
703
+ updateMetrics({
704
+ connectedAt: Date.now(),
705
+ latency: connectionTimeMs,
706
+ transportType: "sse",
707
+ reconnectCount: wasReconnecting ? metrics.reconnectCount + 1 : metrics.reconnectCount
708
+ });
709
+ currentUserIdRef.current = userId;
710
+ if (currentChatKeyRef.current) {
711
+ logger.info("Loading chat after SSE reconnection:", currentChatKeyRef.current);
712
+ sendRestMessage("load", {
713
+ orgId,
714
+ chatKey: currentChatKeyRef.current,
715
+ userId
716
+ }).then((response) => {
717
+ if (response.success && response.data?.chat) {
718
+ const chatEvent = {
719
+ type: "load_chat_response",
720
+ orgId,
721
+ chatKey: currentChatKeyRef.current,
722
+ userId,
723
+ timestamp: Date.now(),
724
+ data: response.data
725
+ };
726
+ emit("load_chat_response", chatEvent);
727
+ if (onMessageRef.current) {
728
+ onMessageRef.current(chatEvent);
729
+ }
730
+ }
731
+ }).catch((err) => {
732
+ logger.error("Failed to load chat after SSE reconnection:", err);
733
+ });
734
+ }
735
+ emit("connected", { userId, wasReconnecting, transport: "sse" });
736
+ resolve();
737
+ };
738
+ const handleSSEMessage = (event) => {
739
+ if (!mountedRef.current) return;
740
+ try {
741
+ const data = JSON.parse(event.data);
742
+ if (!isChatEvent(data)) {
743
+ logger.warn("Received invalid SSE message format:", data);
744
+ return;
745
+ }
746
+ const chatEvent = data;
747
+ logger.debug("SSE message received:", chatEvent.type);
748
+ updateMetrics({
749
+ messagesReceived: metrics.messagesReceived + 1,
750
+ lastMessageAt: Date.now()
751
+ });
752
+ switch (chatEvent.type) {
753
+ case "new_chat_created":
754
+ const newChatKey = chatEvent.data?.chatKey;
755
+ if (newChatKey) {
756
+ logger.info("New chat created with key:", newChatKey);
757
+ currentChatKeyRef.current = newChatKey;
758
+ if (chatCreationPromiseRef.current) {
759
+ chatCreationPromiseRef.current.resolve(newChatKey);
760
+ chatCreationPromiseRef.current = null;
761
+ }
762
+ }
763
+ break;
764
+ case "load_chat_response":
765
+ const chat = chatEvent.data?.chat;
766
+ if (chat && chat.key) {
767
+ logger.info("Chat loaded with key:", chat.key);
768
+ currentChatKeyRef.current = chat.key;
769
+ }
770
+ break;
771
+ case "chat_ended":
772
+ logger.info("Chat ended, clearing key");
773
+ currentChatKeyRef.current = void 0;
774
+ break;
775
+ }
776
+ emit(chatEvent.type || "message", chatEvent);
777
+ if (onMessageRef.current) {
778
+ onMessageRef.current(chatEvent);
779
+ }
780
+ } catch (error2) {
781
+ logger.error("Failed to parse SSE message:", error2);
782
+ }
783
+ };
784
+ eventSource.addEventListener("message", handleSSEMessage);
785
+ eventSource.addEventListener("reconnected", handleSSEMessage);
786
+ eventSource.addEventListener("typing", handleSSEMessage);
787
+ eventSource.addEventListener("stopped_typing", handleSSEMessage);
788
+ eventSource.addEventListener("waiting", handleSSEMessage);
789
+ eventSource.addEventListener("waiting_for_agent", handleSSEMessage);
790
+ eventSource.addEventListener("human_agent_joined", handleSSEMessage);
791
+ eventSource.addEventListener("human_agent_left", handleSSEMessage);
792
+ eventSource.addEventListener("chat_ended", handleSSEMessage);
793
+ eventSource.addEventListener("chat_updated", handleSSEMessage);
794
+ eventSource.addEventListener("load_chat_response", handleSSEMessage);
795
+ eventSource.addEventListener("new_chat_created", handleSSEMessage);
796
+ eventSource.addEventListener("error", handleSSEMessage);
797
+ eventSource.addEventListener("show_csat_survey", handleSSEMessage);
798
+ eventSource.addEventListener("csat_response", handleSSEMessage);
799
+ eventSource.addEventListener("user_suggested_actions", handleSSEMessage);
800
+ eventSource.addEventListener("agent_execution_started", handleSSEMessage);
801
+ eventSource.addEventListener("agent_execution_ended", handleSSEMessage);
802
+ eventSource.addEventListener("agent_context_update", handleSSEMessage);
803
+ eventSource.addEventListener("plan_pending_approval", handleSSEMessage);
804
+ eventSource.addEventListener("step_started", handleSSEMessage);
805
+ eventSource.addEventListener("step_completed", handleSSEMessage);
806
+ eventSource.addEventListener("step_failed", handleSSEMessage);
807
+ eventSource.addEventListener("plan_completed", handleSSEMessage);
808
+ eventSource.addEventListener("skills_changed", handleSSEMessage);
809
+ eventSource.addEventListener("summary_update", handleSSEMessage);
810
+ eventSource.onerror = (error2) => {
811
+ logger.error("SSE error:", error2);
812
+ if (!mountedRef.current) return;
813
+ if (eventSource.readyState === EventSource.CLOSED) {
814
+ const sseError = {
815
+ code: "CONNECTION_FAILED",
816
+ message: "SSE connection failed",
817
+ retryable: true,
818
+ timestamp: Date.now()
819
+ };
820
+ setError(sseError);
821
+ updateMetrics({ lastError: sseError });
822
+ setConnectionState("disconnected");
823
+ emit("disconnected", { reason: "SSE error" });
824
+ if (!intentionalDisconnectRef.current && mountedRef.current) {
825
+ const retryInterval = calculateRetryInterval(retryCountRef.current);
826
+ retryCountRef.current++;
827
+ logger.info(`SSE reconnecting in ${retryInterval}ms (attempt ${retryCountRef.current})`);
828
+ if (reconnectTimeoutRef.current) {
829
+ clearTimeout(reconnectTimeoutRef.current);
830
+ }
831
+ reconnectTimeoutRef.current = setTimeout(() => {
832
+ connect(userId);
833
+ }, retryInterval);
834
+ }
835
+ }
836
+ };
837
+ sseRef.current = eventSource;
838
+ return;
839
+ }
840
+ const wsUrl = `${serverBaseUrl}?orgId=${orgId}&userId=${userId}&clientType=${clientType}&product=${product}`;
632
841
  logger.debug("Connecting to WebSocket:", wsUrl);
633
842
  console.log(`\u23F3 Initiating WebSocket connection to ${serverBaseUrl}...`);
634
843
  const ws = new WebSocket(wsUrl);
@@ -900,7 +1109,7 @@ var useWebSocketChatBase = ({
900
1109
  );
901
1110
  const sendMessage = useCallback(
902
1111
  (event, overrideUserId) => {
903
- return new Promise((resolve, reject) => {
1112
+ return new Promise(async (resolve, reject) => {
904
1113
  if (!mountedRef.current) {
905
1114
  reject(new Error("Component not mounted"));
906
1115
  return;
@@ -909,8 +1118,139 @@ var useWebSocketChatBase = ({
909
1118
  ...event,
910
1119
  timestamp: Date.now()
911
1120
  };
912
- const messageId = `${fullEvent.type}_${fullEvent.timestamp}_${Math.random()}`;
913
1121
  logger.debug("Sending message:", fullEvent.type);
1122
+ if (transportRef.current === "sse") {
1123
+ if (!sseRef.current || sseRef.current.readyState !== EventSource.OPEN) {
1124
+ logger.debug("SSE not connected, attempting to connect");
1125
+ if (connectionState === "disconnected" && overrideUserId) {
1126
+ try {
1127
+ await connect(overrideUserId);
1128
+ } catch (error2) {
1129
+ reject(error2);
1130
+ return;
1131
+ }
1132
+ } else {
1133
+ reject(new Error("SSE not connected"));
1134
+ return;
1135
+ }
1136
+ }
1137
+ try {
1138
+ switch (fullEvent.type) {
1139
+ case "message":
1140
+ await sendRestMessage("send", {
1141
+ orgId: fullEvent.orgId,
1142
+ chatKey: fullEvent.chatKey || currentChatKeyRef.current,
1143
+ userId: fullEvent.userId,
1144
+ message: fullEvent.message
1145
+ });
1146
+ break;
1147
+ case "typing":
1148
+ await sendRestMessage("typing", {
1149
+ orgId: fullEvent.orgId,
1150
+ chatKey: fullEvent.chatKey || currentChatKeyRef.current,
1151
+ userId: fullEvent.userId,
1152
+ typing: true
1153
+ });
1154
+ break;
1155
+ case "stopped_typing":
1156
+ await sendRestMessage("typing", {
1157
+ orgId: fullEvent.orgId,
1158
+ chatKey: fullEvent.chatKey || currentChatKeyRef.current,
1159
+ userId: fullEvent.userId,
1160
+ typing: false
1161
+ });
1162
+ break;
1163
+ case "load_chat":
1164
+ const loadResponse = await sendRestMessage("load", {
1165
+ orgId: fullEvent.orgId,
1166
+ chatKey: fullEvent.chatKey,
1167
+ userId: fullEvent.userId
1168
+ });
1169
+ if (loadResponse.success && loadResponse.data?.chat) {
1170
+ currentChatKeyRef.current = loadResponse.data.chat.key;
1171
+ const chatEvent = {
1172
+ type: "load_chat_response",
1173
+ orgId: fullEvent.orgId,
1174
+ chatKey: loadResponse.data.chat.key,
1175
+ userId: fullEvent.userId,
1176
+ timestamp: Date.now(),
1177
+ data: loadResponse.data
1178
+ };
1179
+ emit("load_chat_response", chatEvent);
1180
+ if (onMessageRef.current) {
1181
+ onMessageRef.current(chatEvent);
1182
+ }
1183
+ }
1184
+ break;
1185
+ case "new_chat":
1186
+ const createResponse = await sendRestMessage("create", {
1187
+ orgId: fullEvent.orgId,
1188
+ userId: fullEvent.userId,
1189
+ metadata: fullEvent.data
1190
+ });
1191
+ if (createResponse.success && createResponse.data?.chatKey) {
1192
+ currentChatKeyRef.current = createResponse.data.chatKey;
1193
+ const newChatEvent = {
1194
+ type: "new_chat_created",
1195
+ orgId: fullEvent.orgId,
1196
+ chatKey: createResponse.data.chatKey,
1197
+ userId: fullEvent.userId,
1198
+ timestamp: Date.now(),
1199
+ data: { chatKey: createResponse.data.chatKey }
1200
+ };
1201
+ emit("new_chat_created", newChatEvent);
1202
+ if (onMessageRef.current) {
1203
+ onMessageRef.current(newChatEvent);
1204
+ }
1205
+ if (chatCreationPromiseRef.current) {
1206
+ chatCreationPromiseRef.current.resolve(createResponse.data.chatKey);
1207
+ chatCreationPromiseRef.current = null;
1208
+ }
1209
+ }
1210
+ break;
1211
+ case "end_chat":
1212
+ await sendRestMessage("end", {
1213
+ orgId: fullEvent.orgId,
1214
+ chatKey: fullEvent.chatKey || currentChatKeyRef.current,
1215
+ userId: fullEvent.userId,
1216
+ data: fullEvent.data
1217
+ });
1218
+ break;
1219
+ case "human_agent_join":
1220
+ await sendRestMessage("agent-join", {
1221
+ orgId: fullEvent.orgId,
1222
+ chatKey: fullEvent.chatKey || currentChatKeyRef.current,
1223
+ user: fullEvent.data?.user
1224
+ });
1225
+ break;
1226
+ case "human_agent_leave":
1227
+ await sendRestMessage("agent-leave", {
1228
+ orgId: fullEvent.orgId,
1229
+ chatKey: fullEvent.chatKey || currentChatKeyRef.current,
1230
+ user: fullEvent.data?.user
1231
+ });
1232
+ break;
1233
+ default:
1234
+ logger.warn("Unrecognized event type for SSE REST:", fullEvent.type);
1235
+ break;
1236
+ }
1237
+ updateMetrics({ messagesSent: metrics.messagesSent + 1 });
1238
+ logger.debug("SSE REST message sent successfully");
1239
+ resolve();
1240
+ } catch (error2) {
1241
+ logger.error("Failed to send SSE REST message:", error2);
1242
+ const sendError = {
1243
+ code: "SEND_FAILED",
1244
+ message: error2 instanceof Error ? error2.message : "Failed to send message",
1245
+ retryable: true,
1246
+ timestamp: Date.now()
1247
+ };
1248
+ setError(sendError);
1249
+ updateMetrics({ lastError: sendError });
1250
+ reject(sendError);
1251
+ }
1252
+ return;
1253
+ }
914
1254
  if (!wsRef.current || wsRef.current.readyState !== WebSocket.OPEN) {
915
1255
  if (addToQueue(fullEvent)) {
916
1256
  logger.debug("Message queued, attempting to connect");
@@ -947,7 +1287,7 @@ var useWebSocketChatBase = ({
947
1287
  }
948
1288
  });
949
1289
  },
950
- [connectionState, connect, addToQueue, logger, metrics, updateMetrics]
1290
+ [connectionState, connect, addToQueue, logger, metrics, updateMetrics, sendRestMessage, emit]
951
1291
  );
952
1292
  const startNewChat = useCallback(
953
1293
  (userId, data) => {
@@ -985,7 +1325,7 @@ var useWebSocketChatBase = ({
985
1325
  );
986
1326
  const disconnect = useCallback(
987
1327
  (intentional = true) => {
988
- logger.info("Disconnecting WebSocket", { intentional });
1328
+ logger.info("Disconnecting", { intentional, transport: transportRef.current });
989
1329
  intentionalDisconnectRef.current = intentional;
990
1330
  cleanup();
991
1331
  setConnectionState("disconnected");
@@ -3662,6 +4002,9 @@ export {
3662
4002
  ChatEventTypeRoomUserJoined,
3663
4003
  ChatEventTypeRoomUserLeft,
3664
4004
  ChatEventTypeRoomsResponse,
4005
+ ChatEventTypeSkillActivate,
4006
+ ChatEventTypeSkillDeactivate,
4007
+ ChatEventTypeSkillsChanged,
3665
4008
  ChatEventTypeStepCompleted,
3666
4009
  ChatEventTypeStepFailed,
3667
4010
  ChatEventTypeStepStarted,