@paymanai/payman-typescript-ask-sdk 1.2.3 → 1.2.5

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.d.mts CHANGED
@@ -144,6 +144,8 @@ type ChatConfig = {
144
144
  api: APIConfig;
145
145
  /** Workflow name */
146
146
  workflowName: string;
147
+ /** User ID for chatStore and activeStreamStore — required for context store messages management. Set to undefined on logout to clear stored messages. */
148
+ userId?: string;
147
149
  /** Workflow version */
148
150
  workflowVersion?: number;
149
151
  /** Stage/Environment */
@@ -194,6 +196,10 @@ type ChatConfig = {
194
196
  isChatDisabled?: boolean;
195
197
  /** Custom component to render when chat is disabled. If not provided, default disabled UI will be shown */
196
198
  disabledComponent?: React.ReactNode;
199
+ /** Pre-populate the chat with messages (e.g. loaded conversation history) */
200
+ initialMessages?: MessageDisplay[];
201
+ /** Resume an existing session by providing its ID */
202
+ initialSessionId?: string;
197
203
  };
198
204
  type ChatCallbacks = {
199
205
  /** Called when a message is sent (before API call) */
@@ -222,6 +228,8 @@ type UseChatReturn = {
222
228
  messages: MessageDisplay[];
223
229
  sendMessage: (userMessage: string) => Promise<void>;
224
230
  clearMessages: () => void;
231
+ /** Prepend older messages to the top of the list (e.g. loaded from history pagination) */
232
+ prependMessages: (messages: MessageDisplay[]) => void;
225
233
  cancelStream: () => void;
226
234
  resetSession: () => void;
227
235
  getSessionId: () => string | undefined;
package/dist/index.d.ts CHANGED
@@ -144,6 +144,8 @@ type ChatConfig = {
144
144
  api: APIConfig;
145
145
  /** Workflow name */
146
146
  workflowName: string;
147
+ /** User ID for chatStore and activeStreamStore — required for context store messages management. Set to undefined on logout to clear stored messages. */
148
+ userId?: string;
147
149
  /** Workflow version */
148
150
  workflowVersion?: number;
149
151
  /** Stage/Environment */
@@ -194,6 +196,10 @@ type ChatConfig = {
194
196
  isChatDisabled?: boolean;
195
197
  /** Custom component to render when chat is disabled. If not provided, default disabled UI will be shown */
196
198
  disabledComponent?: React.ReactNode;
199
+ /** Pre-populate the chat with messages (e.g. loaded conversation history) */
200
+ initialMessages?: MessageDisplay[];
201
+ /** Resume an existing session by providing its ID */
202
+ initialSessionId?: string;
197
203
  };
198
204
  type ChatCallbacks = {
199
205
  /** Called when a message is sent (before API call) */
@@ -222,6 +228,8 @@ type UseChatReturn = {
222
228
  messages: MessageDisplay[];
223
229
  sendMessage: (userMessage: string) => Promise<void>;
224
230
  clearMessages: () => void;
231
+ /** Prepend older messages to the top of the list (e.g. loaded from history pagination) */
232
+ prependMessages: (messages: MessageDisplay[]) => void;
225
233
  cancelStream: () => void;
226
234
  resetSession: () => void;
227
235
  getSessionId: () => string | undefined;
package/dist/index.js CHANGED
@@ -209,11 +209,25 @@ function processStreamEvent(event, state) {
209
209
  }
210
210
  state.activeThinkingText = void 0;
211
211
  }
212
- if ((eventType === "COMPLETED" || eventType === "WORKFLOW_COMPLETED") && event.response !== void 0) {
213
- const content = extractResponseContent(event.response);
212
+ if (eventType === "COMPLETED" || eventType === "WORKFLOW_COMPLETED") {
213
+ let content = extractResponseContent(event.response);
214
+ const trace = event.trace && typeof event.trace === "object" ? event.trace : null;
215
+ if (!content && trace?.workflowMsg && typeof trace.workflowMsg === "string") {
216
+ content = trace.workflowMsg;
217
+ }
218
+ if (!content && trace?.aggregator && typeof trace.aggregator === "object") {
219
+ const agg = trace.aggregator;
220
+ if (typeof agg.response === "string") content = agg.response;
221
+ else content = extractResponseContent(agg.response);
222
+ }
214
223
  if (content) {
215
224
  state.accumulatedContent = content;
216
- state.finalData = event.response;
225
+ state.finalData = event.response ?? event.trace;
226
+ state.hasError = false;
227
+ state.errorMessage = "";
228
+ } else {
229
+ state.hasError = true;
230
+ state.errorMessage = "WORKFLOW_FAILED";
217
231
  }
218
232
  }
219
233
  if (eventType === "STARTED" || eventType === "WORKFLOW_STARTED") ; else if (eventType === "COMPLETED" || eventType === "WORKFLOW_COMPLETED") {
@@ -223,7 +237,6 @@ function processStreamEvent(event, state) {
223
237
  }
224
238
  });
225
239
  } else if (eventType === "INTENT_ERROR") {
226
- state.hasError = true;
227
240
  state.errorMessage = message || event.errorMessage || "An error occurred";
228
241
  const intentStep = state.steps.find(
229
242
  (s) => s.eventType === "INTENT_STARTED" && s.status === "in_progress"
@@ -453,15 +466,12 @@ function processStreamEvent(event, state) {
453
466
  }
454
467
 
455
468
  // src/utils/messageStateManager.ts
469
+ var FRIENDLY_ERROR_MESSAGE = "Oops, something went wrong. Please try again.";
456
470
  function createStreamingMessageUpdate(state) {
457
471
  const hasCompletedContent = state.accumulatedContent && state.finalData !== void 0;
458
472
  return {
459
- streamingContent: state.hasError ? `Oops, something went wrong. Please try again.
460
-
461
- ${state.errorMessage}` : hasCompletedContent ? state.accumulatedContent : "",
462
- content: state.hasError ? `Oops, something went wrong. Please try again.
463
-
464
- ${state.errorMessage}` : "",
473
+ streamingContent: state.hasError ? FRIENDLY_ERROR_MESSAGE : hasCompletedContent ? state.accumulatedContent : "",
474
+ content: state.hasError ? FRIENDLY_ERROR_MESSAGE : "",
465
475
  currentMessage: state.hasError ? void 0 : state.currentMessage,
466
476
  streamProgress: state.hasError ? "error" : "processing",
467
477
  isError: state.hasError,
@@ -484,9 +494,7 @@ function createErrorMessageUpdate(error, state) {
484
494
  isError: !isAborted,
485
495
  isCancelled: isAborted,
486
496
  errorDetails: isAborted ? void 0 : error.message,
487
- content: isAborted ? state.accumulatedContent || "" : state.accumulatedContent || `Oops, something went wrong. Please try again.
488
-
489
- ${error.message}`,
497
+ content: isAborted ? state.accumulatedContent || "" : state.accumulatedContent || FRIENDLY_ERROR_MESSAGE,
490
498
  // Preserve currentMessage when cancelled so UI can show it
491
499
  currentMessage: isAborted ? state.currentMessage || "Thinking..." : void 0,
492
500
  steps: [...state.steps].map((step) => {
@@ -503,9 +511,7 @@ function createFinalMessage(streamingId, state) {
503
511
  id: streamingId,
504
512
  sessionId: state.sessionId,
505
513
  role: "assistant",
506
- content: state.hasError ? `Oops, something went wrong. Please try again.
507
-
508
- ${state.errorMessage}` : state.accumulatedContent || "",
514
+ content: state.hasError ? FRIENDLY_ERROR_MESSAGE : state.accumulatedContent || "",
509
515
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
510
516
  isStreaming: false,
511
517
  streamProgress: state.hasError ? "error" : "completed",
@@ -608,6 +614,84 @@ async function cancelUserAction(config, userActionId) {
608
614
  async function resendUserAction(config, userActionId) {
609
615
  return sendUserActionRequest(config, userActionId, "resend");
610
616
  }
617
+
618
+ // src/utils/chatStore.ts
619
+ var memoryStore = /* @__PURE__ */ new Map();
620
+ var chatStore = {
621
+ get(key) {
622
+ return memoryStore.get(key) ?? [];
623
+ },
624
+ set(key, messages) {
625
+ memoryStore.set(key, messages);
626
+ },
627
+ delete(key) {
628
+ memoryStore.delete(key);
629
+ }
630
+ };
631
+
632
+ // src/utils/activeStreamStore.ts
633
+ var streams = /* @__PURE__ */ new Map();
634
+ var activeStreamStore = {
635
+ has(key) {
636
+ return streams.has(key);
637
+ },
638
+ get(key) {
639
+ const entry = streams.get(key);
640
+ if (!entry) return null;
641
+ return { messages: entry.messages, isWaiting: entry.isWaiting };
642
+ },
643
+ // Called before startStream — registers the controller and initial messages
644
+ start(key, abortController, initialMessages) {
645
+ const existing = streams.get(key);
646
+ streams.set(key, {
647
+ messages: initialMessages,
648
+ isWaiting: true,
649
+ abortController,
650
+ listeners: existing?.listeners ?? /* @__PURE__ */ new Set()
651
+ });
652
+ },
653
+ // Called by the stream on every event — applies the same updater pattern React uses
654
+ applyMessages(key, updater) {
655
+ const entry = streams.get(key);
656
+ if (!entry) return;
657
+ const next = typeof updater === "function" ? updater(entry.messages) : updater;
658
+ entry.messages = next;
659
+ entry.listeners.forEach((l) => l(next, entry.isWaiting));
660
+ },
661
+ setWaiting(key, waiting) {
662
+ const entry = streams.get(key);
663
+ if (!entry) return;
664
+ entry.isWaiting = waiting;
665
+ entry.listeners.forEach((l) => l(entry.messages, waiting));
666
+ },
667
+ // Called when stream completes — persists to chatStore and cleans up
668
+ complete(key) {
669
+ const entry = streams.get(key);
670
+ if (!entry) return;
671
+ entry.isWaiting = false;
672
+ entry.listeners.forEach((l) => l(entry.messages, false));
673
+ const toSave = entry.messages.filter((m) => !m.isStreaming);
674
+ if (toSave.length > 0) chatStore.set(key, toSave);
675
+ streams.delete(key);
676
+ },
677
+ // Subscribe — returns unsubscribe fn. Component calls this on mount, cleanup on unmount.
678
+ subscribe(key, listener) {
679
+ const entry = streams.get(key);
680
+ if (!entry) return () => {
681
+ };
682
+ entry.listeners.add(listener);
683
+ return () => {
684
+ streams.get(key)?.listeners.delete(listener);
685
+ };
686
+ },
687
+ // Explicit user cancel — aborts the controller and removes the entry
688
+ abort(key) {
689
+ const entry = streams.get(key);
690
+ if (!entry) return;
691
+ entry.abortController.abort();
692
+ streams.delete(key);
693
+ }
694
+ };
611
695
  function useStreamManager(config, callbacks, setMessages, setIsWaitingForResponse) {
612
696
  const abortControllerRef = react.useRef(null);
613
697
  const configRef = react.useRef(config);
@@ -615,9 +699,9 @@ function useStreamManager(config, callbacks, setMessages, setIsWaitingForRespons
615
699
  const callbacksRef = react.useRef(callbacks);
616
700
  callbacksRef.current = callbacks;
617
701
  const startStream = react.useCallback(
618
- async (userMessage, streamingId, sessionId) => {
702
+ async (userMessage, streamingId, sessionId, externalAbortController) => {
619
703
  abortControllerRef.current?.abort();
620
- const abortController = new AbortController();
704
+ const abortController = externalAbortController ?? new AbortController();
621
705
  abortControllerRef.current = abortController;
622
706
  const state = {
623
707
  accumulatedContent: "",
@@ -821,13 +905,43 @@ function useStreamManager(config, callbacks, setMessages, setIsWaitingForRespons
821
905
 
822
906
  // src/hooks/useChat.ts
823
907
  function useChat(config, callbacks = {}) {
824
- const [messages, setMessages] = react.useState([]);
908
+ const [messages, setMessages] = react.useState(() => {
909
+ if (config.userId) return chatStore.get(config.userId);
910
+ return config.initialMessages ?? [];
911
+ });
825
912
  const [isWaitingForResponse, setIsWaitingForResponse] = react.useState(false);
826
- const sessionIdRef = react.useRef(void 0);
913
+ const sessionIdRef = react.useRef(
914
+ config.userId ? chatStore.get(config.userId).find((m) => m.sessionId)?.sessionId ?? config.initialSessionId ?? void 0 : config.initialSessionId ?? void 0
915
+ );
916
+ const prevUserIdRef = react.useRef(config.userId);
827
917
  const callbacksRef = react.useRef(callbacks);
828
918
  callbacksRef.current = callbacks;
829
919
  const configRef = react.useRef(config);
830
920
  configRef.current = config;
921
+ const messagesRef = react.useRef(messages);
922
+ messagesRef.current = messages;
923
+ const storeAwareSetMessages = react.useCallback(
924
+ (updater) => {
925
+ const { userId } = configRef.current;
926
+ if (userId && activeStreamStore.has(userId)) {
927
+ activeStreamStore.applyMessages(userId, updater);
928
+ }
929
+ setMessages(updater);
930
+ },
931
+ // eslint-disable-next-line react-hooks/exhaustive-deps
932
+ []
933
+ );
934
+ const storeAwareSetIsWaiting = react.useCallback(
935
+ (waiting) => {
936
+ const { userId } = configRef.current;
937
+ if (userId && activeStreamStore.has(userId)) {
938
+ activeStreamStore.setWaiting(userId, waiting);
939
+ }
940
+ setIsWaitingForResponse(waiting);
941
+ },
942
+ // eslint-disable-next-line react-hooks/exhaustive-deps
943
+ []
944
+ );
831
945
  const [userActionState, setUserActionState] = react.useState({
832
946
  request: null,
833
947
  result: null,
@@ -870,8 +984,8 @@ function useChat(config, callbacks = {}) {
870
984
  const { startStream, cancelStream: cancelStreamManager, abortControllerRef } = useStreamManager(
871
985
  config,
872
986
  wrappedCallbacks,
873
- setMessages,
874
- setIsWaitingForResponse
987
+ storeAwareSetMessages,
988
+ storeAwareSetIsWaiting
875
989
  );
876
990
  const sendMessage = react.useCallback(
877
991
  async (userMessage) => {
@@ -908,11 +1022,21 @@ function useChat(config, callbacks = {}) {
908
1022
  currentMessage: void 0
909
1023
  };
910
1024
  setMessages((prev) => [...prev, streamingMsg]);
1025
+ const abortController = new AbortController();
1026
+ const { userId } = configRef.current;
1027
+ if (userId) {
1028
+ const initialMessages = [...messagesRef.current, userMsg, streamingMsg];
1029
+ activeStreamStore.start(userId, abortController, initialMessages);
1030
+ }
911
1031
  const newSessionId = await startStream(
912
1032
  userMessage,
913
1033
  streamingId,
914
- sessionIdRef.current
1034
+ sessionIdRef.current,
1035
+ abortController
915
1036
  );
1037
+ if (userId) {
1038
+ activeStreamStore.complete(userId);
1039
+ }
916
1040
  if (newSessionId && newSessionId !== sessionIdRef.current) {
917
1041
  sessionIdRef.current = newSessionId;
918
1042
  }
@@ -920,9 +1044,18 @@ function useChat(config, callbacks = {}) {
920
1044
  [startStream]
921
1045
  );
922
1046
  const clearMessages = react.useCallback(() => {
1047
+ if (configRef.current.userId) {
1048
+ chatStore.delete(configRef.current.userId);
1049
+ }
923
1050
  setMessages([]);
924
1051
  }, []);
1052
+ const prependMessages = react.useCallback((msgs) => {
1053
+ setMessages((prev) => [...msgs, ...prev]);
1054
+ }, []);
925
1055
  const cancelStream = react.useCallback(() => {
1056
+ if (configRef.current.userId) {
1057
+ activeStreamStore.abort(configRef.current.userId);
1058
+ }
926
1059
  cancelStreamManager();
927
1060
  setIsWaitingForResponse(false);
928
1061
  setUserActionState((prev) => ({ ...prev, request: null, result: null }));
@@ -942,6 +1075,10 @@ function useChat(config, callbacks = {}) {
942
1075
  );
943
1076
  }, [cancelStreamManager]);
944
1077
  const resetSession = react.useCallback(() => {
1078
+ if (configRef.current.userId) {
1079
+ activeStreamStore.abort(configRef.current.userId);
1080
+ chatStore.delete(configRef.current.userId);
1081
+ }
945
1082
  setMessages([]);
946
1083
  sessionIdRef.current = void 0;
947
1084
  abortControllerRef.current?.abort();
@@ -1004,10 +1141,48 @@ function useChat(config, callbacks = {}) {
1004
1141
  throw error;
1005
1142
  }
1006
1143
  }, []);
1144
+ react.useEffect(() => {
1145
+ const { userId } = config;
1146
+ if (!userId) return;
1147
+ const unsubscribe = activeStreamStore.subscribe(userId, (msgs, isWaiting) => {
1148
+ setMessages(msgs);
1149
+ setIsWaitingForResponse(isWaiting);
1150
+ });
1151
+ const active = activeStreamStore.get(userId);
1152
+ if (active) {
1153
+ setMessages(active.messages);
1154
+ setIsWaitingForResponse(active.isWaiting);
1155
+ }
1156
+ return unsubscribe;
1157
+ }, []);
1158
+ react.useEffect(() => {
1159
+ if (!config.userId) return;
1160
+ const toSave = messages.filter((m) => !m.isStreaming);
1161
+ if (toSave.length > 0) {
1162
+ chatStore.set(config.userId, toSave);
1163
+ }
1164
+ }, [messages, config.userId]);
1165
+ react.useEffect(() => {
1166
+ const prevUserId = prevUserIdRef.current;
1167
+ prevUserIdRef.current = config.userId;
1168
+ if (prevUserId === config.userId) return;
1169
+ if (prevUserId && !config.userId) {
1170
+ chatStore.delete(prevUserId);
1171
+ setMessages([]);
1172
+ sessionIdRef.current = void 0;
1173
+ setIsWaitingForResponse(false);
1174
+ setUserActionState({ request: null, result: null, clearOtpTrigger: 0 });
1175
+ } else if (config.userId) {
1176
+ const stored = chatStore.get(config.userId);
1177
+ setMessages(stored);
1178
+ sessionIdRef.current = stored.find((m) => m.sessionId)?.sessionId;
1179
+ }
1180
+ }, [config.userId]);
1007
1181
  return {
1008
1182
  messages,
1009
1183
  sendMessage,
1010
1184
  clearMessages,
1185
+ prependMessages,
1011
1186
  cancelStream,
1012
1187
  resetSession,
1013
1188
  getSessionId,