@paymanai/payman-typescript-ask-sdk 1.0.2 → 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.
@@ -137,10 +137,10 @@ async function streamWorkflowEvents(url, body, headers, options = {}) {
137
137
  // src/utils/eventProcessor.ts
138
138
  function getEventMessage(event) {
139
139
  if (event.message?.trim()) {
140
- return event.message;
140
+ return event.message.trim();
141
141
  }
142
142
  if (event.errorMessage?.trim()) {
143
- return event.errorMessage;
143
+ return event.errorMessage.trim();
144
144
  }
145
145
  const eventType = event.eventType;
146
146
  switch (eventType) {
@@ -168,6 +168,20 @@ function getEventMessage(event) {
168
168
  case "WORKFLOW_ERROR":
169
169
  case "INTENT_ERROR":
170
170
  return event.errorMessage || "An error occurred";
171
+ case "USER_ACTION_REQUIRED":
172
+ return "Waiting for verification...";
173
+ case "USER_ACTION_SUCCESS":
174
+ return "Verification successful";
175
+ case "USER_ACTION_EXPIRED":
176
+ return "Verification expired";
177
+ case "USER_ACTION_INVALID":
178
+ return "Invalid input, please try again";
179
+ case "USER_ACTION_REJECTED":
180
+ return "Verification rejected";
181
+ case "USER_ACTION_RESENT":
182
+ return "Verification code resent";
183
+ case "USER_ACTION_FAILED":
184
+ return "Verification failed";
171
185
  default:
172
186
  return eventType;
173
187
  }
@@ -194,6 +208,14 @@ function extractResponseContent(response) {
194
208
  }
195
209
  return "";
196
210
  }
211
+ function completeLastInProgressStep(steps) {
212
+ for (let i = steps.length - 1; i >= 0; i--) {
213
+ if (steps[i].status === "in_progress") {
214
+ steps[i].status = "completed";
215
+ return;
216
+ }
217
+ }
218
+ }
197
219
  function processStreamEvent(event, state) {
198
220
  const eventType = event.eventType;
199
221
  const message = getEventMessage(event);
@@ -293,6 +315,110 @@ function processStreamEvent(event, state) {
293
315
  });
294
316
  state.currentExecutingStepId = stepId;
295
317
  }
318
+ } else if (eventType === "USER_ACTION_REQUIRED") {
319
+ completeLastInProgressStep(state.steps);
320
+ if (event.userActionRequest) {
321
+ state.userActionRequest = {
322
+ userActionId: event.userActionRequest.userActionId,
323
+ message: event.userActionRequest.message,
324
+ requestedSchema: event.userActionRequest.requestedSchema
325
+ };
326
+ }
327
+ state.userActionPending = true;
328
+ const stepId = `step-${state.stepCounter++}`;
329
+ state.steps.push({
330
+ id: stepId,
331
+ eventType,
332
+ message,
333
+ status: "in_progress",
334
+ timestamp: Date.now(),
335
+ elapsedMs: event.elapsedMs
336
+ });
337
+ state.currentExecutingStepId = stepId;
338
+ } else if (eventType === "USER_ACTION_SUCCESS") {
339
+ completeLastInProgressStep(state.steps);
340
+ state.userActionRequest = void 0;
341
+ state.userActionPending = false;
342
+ state.userActionResult = "approved";
343
+ const stepId = `step-${state.stepCounter++}`;
344
+ state.steps.push({
345
+ id: stepId,
346
+ eventType,
347
+ message,
348
+ status: "completed",
349
+ timestamp: Date.now(),
350
+ elapsedMs: event.elapsedMs
351
+ });
352
+ } else if (eventType === "USER_ACTION_INVALID") {
353
+ completeLastInProgressStep(state.steps);
354
+ const errorStepId = `step-${state.stepCounter++}`;
355
+ state.steps.push({
356
+ id: errorStepId,
357
+ eventType,
358
+ message,
359
+ status: "error",
360
+ timestamp: Date.now(),
361
+ elapsedMs: event.elapsedMs
362
+ });
363
+ const retryStepId = `step-${state.stepCounter++}`;
364
+ state.steps.push({
365
+ id: retryStepId,
366
+ eventType: "USER_ACTION_REQUIRED",
367
+ message: "Waiting for verification...",
368
+ status: "in_progress",
369
+ timestamp: Date.now()
370
+ });
371
+ state.currentExecutingStepId = retryStepId;
372
+ } else if (eventType === "USER_ACTION_EXPIRED") {
373
+ completeLastInProgressStep(state.steps);
374
+ state.userActionRequest = void 0;
375
+ state.userActionPending = false;
376
+ const stepId = `step-${state.stepCounter++}`;
377
+ state.steps.push({
378
+ id: stepId,
379
+ eventType,
380
+ message,
381
+ status: "error",
382
+ timestamp: Date.now(),
383
+ elapsedMs: event.elapsedMs
384
+ });
385
+ } else if (eventType === "USER_ACTION_REJECTED") {
386
+ completeLastInProgressStep(state.steps);
387
+ state.userActionRequest = void 0;
388
+ state.userActionPending = false;
389
+ state.userActionResult = "rejected";
390
+ const stepId = `step-${state.stepCounter++}`;
391
+ state.steps.push({
392
+ id: stepId,
393
+ eventType,
394
+ message,
395
+ status: "completed",
396
+ timestamp: Date.now(),
397
+ elapsedMs: event.elapsedMs
398
+ });
399
+ } else if (eventType === "USER_ACTION_RESENT") {
400
+ const stepId = `step-${state.stepCounter++}`;
401
+ state.steps.push({
402
+ id: stepId,
403
+ eventType,
404
+ message,
405
+ status: "completed",
406
+ timestamp: Date.now(),
407
+ elapsedMs: event.elapsedMs
408
+ });
409
+ } else if (eventType === "USER_ACTION_FAILED") {
410
+ completeLastInProgressStep(state.steps);
411
+ state.userActionRequest = void 0;
412
+ state.userActionPending = false;
413
+ const stepId = `step-${state.stepCounter++}`;
414
+ state.steps.push({
415
+ id: stepId,
416
+ eventType,
417
+ message,
418
+ status: "error",
419
+ timestamp: Date.now(),
420
+ elapsedMs: event.elapsedMs
421
+ });
296
422
  }
297
423
  return state;
298
424
  }
@@ -314,9 +440,9 @@ ${state.errorMessage}` : "",
314
440
  executionId: state.executionId,
315
441
  sessionId: state.sessionId,
316
442
  steps: state.hasError ? [] : [...state.steps],
317
- // Don't show steps on error
318
443
  currentExecutingStepId: state.hasError ? void 0 : state.currentExecutingStepId,
319
- isCancelled: false
444
+ isCancelled: false,
445
+ userActionResult: state.userActionResult
320
446
  };
321
447
  }
322
448
  function createErrorMessageUpdate(error, state) {
@@ -357,9 +483,9 @@ ${state.errorMessage}` : state.accumulatedContent || "",
357
483
  executionId: state.executionId,
358
484
  tracingData: state.finalData,
359
485
  steps: state.hasError ? [] : [...state.steps],
360
- // Don't show steps on error
361
486
  isCancelled: false,
362
- currentExecutingStepId: void 0
487
+ currentExecutingStepId: void 0,
488
+ userActionResult: state.userActionResult
363
489
  };
364
490
  }
365
491
  function createCancelledMessageUpdate(steps, currentMessage) {
@@ -405,6 +531,14 @@ function buildStreamingUrl(config) {
405
531
  }
406
532
  return `${config.api.baseUrl}${endpoint}?${queryParams.toString()}`;
407
533
  }
534
+ function buildUserActionUrl(config, userActionId, action) {
535
+ const endpoint = config.api.streamEndpoint || "/api/workflows/ask/stream";
536
+ const [endpointPath] = endpoint.split("?");
537
+ const normalizedEndpointPath = endpointPath.replace(/\/+$/, "");
538
+ const basePath = normalizedEndpointPath.endsWith("/stream") ? normalizedEndpointPath.slice(0, -"/stream".length) : normalizedEndpointPath;
539
+ const encodedUserActionId = encodeURIComponent(userActionId);
540
+ return `${config.api.baseUrl}${basePath}/user-action/${encodedUserActionId}/${action}`;
541
+ }
408
542
  function buildRequestHeaders(config) {
409
543
  const headers = {
410
544
  ...config.api.headers
@@ -414,8 +548,39 @@ function buildRequestHeaders(config) {
414
548
  }
415
549
  return headers;
416
550
  }
551
+
552
+ // src/utils/userActionClient.ts
553
+ async function sendUserActionRequest(config, userActionId, action, data) {
554
+ const url = buildUserActionUrl(config, userActionId, action);
555
+ const baseHeaders = buildRequestHeaders(config);
556
+ const hasBody = data !== void 0;
557
+ const headers = hasBody ? { "Content-Type": "application/json", ...baseHeaders } : baseHeaders;
558
+ const response = await fetch(url, {
559
+ method: "POST",
560
+ headers,
561
+ body: hasBody ? JSON.stringify(data) : void 0
562
+ });
563
+ if (!response.ok) {
564
+ const errorText = await response.text();
565
+ throw new Error(`HTTP ${response.status}: ${errorText}`);
566
+ }
567
+ return await response.json();
568
+ }
569
+ async function submitUserAction(config, userActionId, data) {
570
+ return sendUserActionRequest(config, userActionId, "submit", data);
571
+ }
572
+ async function cancelUserAction(config, userActionId) {
573
+ return sendUserActionRequest(config, userActionId, "cancel");
574
+ }
575
+ async function resendUserAction(config, userActionId) {
576
+ return sendUserActionRequest(config, userActionId, "resend");
577
+ }
417
578
  function useStreamManager(config, callbacks, setMessages, setIsWaitingForResponse) {
418
579
  const abortControllerRef = react.useRef(null);
580
+ const configRef = react.useRef(config);
581
+ configRef.current = config;
582
+ const callbacksRef = react.useRef(callbacks);
583
+ callbacksRef.current = callbacks;
419
584
  const startStream = react.useCallback(
420
585
  async (userMessage, streamingId, sessionId) => {
421
586
  abortControllerRef.current?.abort();
@@ -430,11 +595,15 @@ function useStreamManager(config, callbacks, setMessages, setIsWaitingForRespons
430
595
  stepCounter: 0,
431
596
  currentExecutingStepId: void 0,
432
597
  hasError: false,
433
- errorMessage: ""
598
+ errorMessage: "",
599
+ userActionRequest: void 0,
600
+ userActionPending: false,
601
+ userActionResult: void 0
434
602
  };
435
- const requestBody = buildRequestBody(config, userMessage, sessionId);
436
- const url = buildStreamingUrl(config);
437
- const headers = buildRequestHeaders(config);
603
+ const currentConfig = configRef.current;
604
+ const requestBody = buildRequestBody(currentConfig, userMessage, sessionId);
605
+ const url = buildStreamingUrl(currentConfig);
606
+ const headers = buildRequestHeaders(currentConfig);
438
607
  try {
439
608
  await streamWorkflowEvents(url, requestBody, headers, {
440
609
  signal: abortController.signal,
@@ -445,7 +614,15 @@ function useStreamManager(config, callbacks, setMessages, setIsWaitingForRespons
445
614
  if (event.executionId) state.executionId = event.executionId;
446
615
  if (event.sessionId) state.currentSessionId = event.sessionId;
447
616
  processStreamEvent(event, state);
448
- const currentMessage = event.message?.trim() || event.errorMessage?.trim();
617
+ const eventType = event.eventType;
618
+ if (eventType === "USER_ACTION_REQUIRED" && state.userActionRequest) {
619
+ callbacksRef.current.onUserActionRequired?.(state.userActionRequest);
620
+ } else if (eventType.startsWith("USER_ACTION_") && eventType !== "USER_ACTION_REQUIRED") {
621
+ const msg = event.message?.trim() || event.errorMessage?.trim() || getEventMessage(event);
622
+ callbacksRef.current.onUserActionEvent?.(eventType, msg);
623
+ }
624
+ const rawMessage = event.message?.trim() || event.errorMessage?.trim();
625
+ const currentMessage = rawMessage || (event.eventType?.startsWith("USER_ACTION_") ? getEventMessage(event) : void 0);
449
626
  setMessages(
450
627
  (prev) => prev.map(
451
628
  (msg) => msg.id === streamingId ? {
@@ -461,7 +638,7 @@ function useStreamManager(config, callbacks, setMessages, setIsWaitingForRespons
461
638
  onError: (error) => {
462
639
  setIsWaitingForResponse(false);
463
640
  if (error.name !== "AbortError") {
464
- callbacks.onError?.(error);
641
+ callbacksRef.current.onError?.(error);
465
642
  }
466
643
  setMessages(
467
644
  (prev) => prev.map(
@@ -475,7 +652,7 @@ function useStreamManager(config, callbacks, setMessages, setIsWaitingForRespons
475
652
  onComplete: () => {
476
653
  setIsWaitingForResponse(false);
477
654
  if (state.currentSessionId && state.currentSessionId !== sessionId) {
478
- callbacks.onSessionIdChange?.(state.currentSessionId);
655
+ callbacksRef.current.onSessionIdChange?.(state.currentSessionId);
479
656
  }
480
657
  const finalMessage = createFinalMessage(streamingId, {
481
658
  ...state,
@@ -486,14 +663,14 @@ function useStreamManager(config, callbacks, setMessages, setIsWaitingForRespons
486
663
  (msg) => msg.id === streamingId ? finalMessage : msg
487
664
  )
488
665
  );
489
- callbacks.onStreamComplete?.(finalMessage);
666
+ callbacksRef.current.onStreamComplete?.(finalMessage);
490
667
  }
491
668
  });
492
669
  return state.currentSessionId;
493
670
  } catch (error) {
494
671
  setIsWaitingForResponse(false);
495
672
  if (error.name !== "AbortError") {
496
- callbacks.onError?.(error);
673
+ callbacksRef.current.onError?.(error);
497
674
  }
498
675
  setMessages(
499
676
  (prev) => prev.map(
@@ -506,7 +683,7 @@ function useStreamManager(config, callbacks, setMessages, setIsWaitingForRespons
506
683
  return state.currentSessionId;
507
684
  }
508
685
  },
509
- [config, callbacks, setMessages, setIsWaitingForResponse]
686
+ [setMessages, setIsWaitingForResponse]
510
687
  );
511
688
  const cancelStream = react.useCallback(() => {
512
689
  abortControllerRef.current?.abort();
@@ -523,18 +700,61 @@ function useChat(config, callbacks = {}) {
523
700
  const [messages, setMessages] = react.useState([]);
524
701
  const [isWaitingForResponse, setIsWaitingForResponse] = react.useState(false);
525
702
  const sessionIdRef = react.useRef(void 0);
703
+ const callbacksRef = react.useRef(callbacks);
704
+ callbacksRef.current = callbacks;
705
+ const configRef = react.useRef(config);
706
+ configRef.current = config;
707
+ const [userActionState, setUserActionState] = react.useState({
708
+ request: null,
709
+ result: null,
710
+ clearOtpTrigger: 0
711
+ });
712
+ const userActionStateRef = react.useRef(userActionState);
713
+ userActionStateRef.current = userActionState;
714
+ const wrappedCallbacks = react.useMemo(() => ({
715
+ ...callbacksRef.current,
716
+ onMessageSent: (message) => callbacksRef.current.onMessageSent?.(message),
717
+ onStreamStart: () => callbacksRef.current.onStreamStart?.(),
718
+ onStreamComplete: (message) => callbacksRef.current.onStreamComplete?.(message),
719
+ onError: (error) => callbacksRef.current.onError?.(error),
720
+ onExecutionTraceClick: (data) => callbacksRef.current.onExecutionTraceClick?.(data),
721
+ onSessionIdChange: (sessionId) => callbacksRef.current.onSessionIdChange?.(sessionId),
722
+ onUserActionRequired: (request) => {
723
+ setUserActionState((prev) => ({ ...prev, request, result: null }));
724
+ callbacksRef.current.onUserActionRequired?.(request);
725
+ },
726
+ onUserActionEvent: (eventType, message) => {
727
+ switch (eventType) {
728
+ case "USER_ACTION_SUCCESS":
729
+ setUserActionState((prev) => ({ ...prev, request: null, result: "approved" }));
730
+ break;
731
+ case "USER_ACTION_REJECTED":
732
+ setUserActionState((prev) => ({ ...prev, request: null, result: "rejected" }));
733
+ break;
734
+ case "USER_ACTION_EXPIRED":
735
+ case "USER_ACTION_FAILED":
736
+ setUserActionState((prev) => ({ ...prev, request: null }));
737
+ break;
738
+ case "USER_ACTION_INVALID":
739
+ setUserActionState((prev) => ({ ...prev, clearOtpTrigger: prev.clearOtpTrigger + 1 }));
740
+ break;
741
+ }
742
+ callbacksRef.current.onUserActionEvent?.(eventType, message);
743
+ }
744
+ // eslint-disable-next-line react-hooks/exhaustive-deps
745
+ }), []);
526
746
  const { startStream, cancelStream: cancelStreamManager, abortControllerRef } = useStreamManager(
527
747
  config,
528
- callbacks,
748
+ wrappedCallbacks,
529
749
  setMessages,
530
750
  setIsWaitingForResponse
531
751
  );
532
752
  const sendMessage = react.useCallback(
533
753
  async (userMessage) => {
534
754
  if (!userMessage.trim()) return;
535
- if (!sessionIdRef.current && config.autoGenerateSessionId !== false) {
755
+ if (!sessionIdRef.current && configRef.current.autoGenerateSessionId !== false) {
536
756
  sessionIdRef.current = generateId();
537
- callbacks.onSessionIdChange?.(sessionIdRef.current);
757
+ callbacksRef.current.onSessionIdChange?.(sessionIdRef.current);
538
758
  }
539
759
  const userMessageId = `user-${Date.now()}`;
540
760
  const userMsg = {
@@ -545,9 +765,9 @@ function useChat(config, callbacks = {}) {
545
765
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
546
766
  };
547
767
  setMessages((prev) => [...prev, userMsg]);
548
- callbacks.onMessageSent?.(userMessage);
768
+ callbacksRef.current.onMessageSent?.(userMessage);
549
769
  setIsWaitingForResponse(true);
550
- callbacks.onStreamStart?.();
770
+ callbacksRef.current.onStreamStart?.();
551
771
  const streamingId = `assistant-${Date.now()}`;
552
772
  const streamingMsg = {
553
773
  id: streamingId,
@@ -573,7 +793,7 @@ function useChat(config, callbacks = {}) {
573
793
  sessionIdRef.current = newSessionId;
574
794
  }
575
795
  },
576
- [config, callbacks, startStream, setMessages, setIsWaitingForResponse]
796
+ [startStream]
577
797
  );
578
798
  const clearMessages = react.useCallback(() => {
579
799
  setMessages([]);
@@ -581,6 +801,7 @@ function useChat(config, callbacks = {}) {
581
801
  const cancelStream = react.useCallback(() => {
582
802
  cancelStreamManager();
583
803
  setIsWaitingForResponse(false);
804
+ setUserActionState((prev) => ({ ...prev, request: null, result: null }));
584
805
  setMessages(
585
806
  (prev) => prev.map((msg) => {
586
807
  if (msg.isStreaming) {
@@ -601,6 +822,7 @@ function useChat(config, callbacks = {}) {
601
822
  sessionIdRef.current = void 0;
602
823
  abortControllerRef.current?.abort();
603
824
  setIsWaitingForResponse(false);
825
+ setUserActionState({ request: null, result: null, clearOtpTrigger: 0 });
604
826
  }, []);
605
827
  const getSessionId = react.useCallback(() => {
606
828
  return sessionIdRef.current;
@@ -608,6 +830,56 @@ function useChat(config, callbacks = {}) {
608
830
  const getMessages = react.useCallback(() => {
609
831
  return messages;
610
832
  }, [messages]);
833
+ const approveUserAction = react.useCallback(
834
+ async (otp) => {
835
+ const request = userActionStateRef.current.request;
836
+ if (!request) return;
837
+ try {
838
+ await submitUserAction(configRef.current, request.userActionId, { otp });
839
+ } catch (error) {
840
+ setUserActionState((prev) => ({
841
+ ...prev,
842
+ clearOtpTrigger: prev.clearOtpTrigger + 1
843
+ }));
844
+ callbacksRef.current.onError?.(error);
845
+ throw error;
846
+ }
847
+ },
848
+ []
849
+ );
850
+ const rejectUserAction = react.useCallback(async () => {
851
+ const request = userActionStateRef.current.request;
852
+ if (!request) return;
853
+ try {
854
+ setMessages((prev) => {
855
+ let lastStreamingIdx = -1;
856
+ for (let i = prev.length - 1; i >= 0; i--) {
857
+ if (prev[i].role === "assistant" && prev[i].isStreaming) {
858
+ lastStreamingIdx = i;
859
+ break;
860
+ }
861
+ }
862
+ if (lastStreamingIdx === -1) return prev;
863
+ return prev.map(
864
+ (msg, i) => i === lastStreamingIdx ? { ...msg, currentMessage: "Rejecting..." } : msg
865
+ );
866
+ });
867
+ await cancelUserAction(configRef.current, request.userActionId);
868
+ } catch (error) {
869
+ callbacksRef.current.onError?.(error);
870
+ throw error;
871
+ }
872
+ }, []);
873
+ const resendOtp = react.useCallback(async () => {
874
+ const request = userActionStateRef.current.request;
875
+ if (!request) return;
876
+ try {
877
+ await resendUserAction(configRef.current, request.userActionId);
878
+ } catch (error) {
879
+ callbacksRef.current.onError?.(error);
880
+ throw error;
881
+ }
882
+ }, []);
611
883
  return {
612
884
  messages,
613
885
  sendMessage,
@@ -617,12 +889,20 @@ function useChat(config, callbacks = {}) {
617
889
  getSessionId,
618
890
  getMessages,
619
891
  isWaitingForResponse,
620
- sessionId: sessionIdRef.current
892
+ sessionId: sessionIdRef.current,
893
+ // User action (OTP) state and methods
894
+ userActionState,
895
+ approveUserAction,
896
+ rejectUserAction,
897
+ resendOtp
621
898
  };
622
899
  }
623
900
 
901
+ exports.cancelUserAction = cancelUserAction;
624
902
  exports.generateId = generateId;
903
+ exports.resendUserAction = resendUserAction;
625
904
  exports.streamWorkflowEvents = streamWorkflowEvents;
905
+ exports.submitUserAction = submitUserAction;
626
906
  exports.useChat = useChat;
627
907
  //# sourceMappingURL=index.native.js.map
628
908
  //# sourceMappingURL=index.native.js.map