@paymanai/payman-typescript-ask-sdk 4.0.9 → 4.0.10

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.js CHANGED
@@ -2,7 +2,9 @@
2
2
 
3
3
  var react = require('react');
4
4
 
5
- // src/hooks/useChatV2.ts
5
+ var __defProp = Object.defineProperty;
6
+ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
7
+ var __publicField = (obj, key, value) => __defNormalProp(obj, key + "" , value);
6
8
 
7
9
  // src/utils/generateId.ts
8
10
  function generateId() {
@@ -258,31 +260,27 @@ function normalizeEvent(event) {
258
260
  return event;
259
261
  }
260
262
  }
261
- function isUserActionPrompt(text) {
262
- return /\benter\b.+\b(code|otp)\b/i.test(text) || /\b(authorization|verification)\s+code\b/i.test(text) || /\bsent\s+to\s+your\b/i.test(text);
263
+ function isVerificationSchema(schema) {
264
+ const props = schema?.properties;
265
+ if (!props) return false;
266
+ const keys = Object.keys(props);
267
+ return keys.length === 1 && keys[0] === "verificationCode";
263
268
  }
264
- function getUserActionDisplayMessage(eventType, rawMessage) {
265
- const raw = rawMessage?.trim();
266
- const safeRaw = raw && !isUserActionPrompt(raw) ? raw : "";
267
- switch (eventType) {
268
- case "USER_ACTION_REQUIRED":
269
- return raw || "Waiting for verification...";
270
- case "USER_ACTION_SUCCESS":
271
- return safeRaw || "Verification approved";
272
- case "USER_ACTION_INVALID":
273
- return safeRaw || "Invalid code. Please try again.";
274
- case "USER_ACTION_REJECTED":
275
- return safeRaw || "Verification cancelled";
276
- case "USER_ACTION_EXPIRED":
277
- return safeRaw || "Verification expired";
278
- case "USER_ACTION_RESENT":
279
- return safeRaw || "Verification code resent";
280
- case "USER_ACTION_FAILED":
281
- return safeRaw || "Verification failed";
269
+ function classifyUserActionKind(action, schema) {
270
+ switch ((action || "").toLowerCase()) {
271
+ case "userverificationrequest":
272
+ return "verification";
273
+ case "usernotificationrequest":
274
+ return "notification";
275
+ case "userformrequest":
276
+ return isVerificationSchema(schema) ? "verification" : "form";
282
277
  default:
283
- return safeRaw || raw || eventType;
278
+ return isVerificationSchema(schema) ? "verification" : "form";
284
279
  }
285
280
  }
281
+ function userActionHeader(kind) {
282
+ return kind === "verification" ? "**Verification required**" : "**Action required**";
283
+ }
286
284
  function workingPhaseDetailForDisplay(raw) {
287
285
  const t = raw.trim();
288
286
  if (!t) return "";
@@ -342,18 +340,32 @@ function createInitialV2State() {
342
340
  executionId: void 0,
343
341
  hasError: false,
344
342
  errorMessage: "",
345
- userActionRequest: void 0,
346
- userActionPending: false,
347
- userActionResult: void 0,
343
+ userActions: [],
344
+ notifications: [],
345
+ lastUserAction: void 0,
346
+ lastNotification: void 0,
348
347
  finalData: void 0,
349
348
  steps: [],
350
349
  stepCounter: 0,
351
350
  currentExecutingStepId: void 0
352
351
  };
353
352
  }
353
+ function upsertUserAction(state, req) {
354
+ const active = { ...req, status: "pending" };
355
+ const matchIdx = state.userActions.findIndex(
356
+ (p) => req.toolCallId ? p.toolCallId === req.toolCallId : p.userActionId === req.userActionId
357
+ );
358
+ if (matchIdx >= 0) {
359
+ state.userActions[matchIdx] = active;
360
+ } else {
361
+ state.userActions.push(active);
362
+ }
363
+ }
354
364
  function processStreamEventV2(rawEvent, state) {
355
365
  const event = normalizeEvent(rawEvent);
356
366
  const eventType = event.eventType;
367
+ state.lastUserAction = void 0;
368
+ state.lastNotification = void 0;
357
369
  if (typeof eventType === "string" && eventType.toUpperCase() === "KEEP_ALIVE") {
358
370
  if (event.executionId) state.executionId = event.executionId;
359
371
  if (event.sessionId) state.sessionId = event.sessionId;
@@ -575,152 +587,72 @@ function processStreamEventV2(rawEvent, state) {
575
587
  break;
576
588
  }
577
589
  case "USER_ACTION_REQUIRED": {
578
- completeLastInProgressStep(state.steps);
579
- if (event.userActionRequest) {
580
- state.userActionRequest = {
581
- userActionId: event.userActionRequest.userActionId,
582
- userActionType: event.userActionRequest.userActionType,
583
- message: event.userActionRequest.message,
584
- requestedSchema: event.userActionRequest.requestedSchema,
585
- metadata: event.userActionRequest.metadata
590
+ const rawAction = typeof event.action === "string" ? event.action : void 0;
591
+ const schema = event.requestedSchema;
592
+ const kind = classifyUserActionKind(rawAction, schema);
593
+ const promptMessage = typeof event.message === "string" && event.message.trim() || message || "";
594
+ const userActionId = typeof event.userActionId === "string" ? event.userActionId : "";
595
+ if (kind === "notification") {
596
+ const notification = {
597
+ id: userActionId || `note-${state.stepCounter++}`,
598
+ message: promptMessage
586
599
  };
600
+ state.notifications.push(notification);
601
+ state.lastNotification = notification;
602
+ if (promptMessage) addThinkingDetail(state, promptMessage);
603
+ state.lastEventType = eventType;
604
+ break;
587
605
  }
588
- state.userActionPending = true;
589
- const req = event.userActionRequest;
590
- const displayMessage = getUserActionDisplayMessage(
591
- eventType,
592
- req?.message || message
593
- );
594
- if (req) {
595
- addThinkingLine(state, "**Verification Required**", displayMessage);
606
+ if (!userActionId) {
607
+ state.lastEventType = eventType;
608
+ break;
596
609
  }
597
- const stepId = `step-${state.stepCounter++}`;
598
- state.steps.push({
599
- id: stepId,
600
- eventType,
601
- message: displayMessage,
602
- status: "in_progress",
603
- timestamp: Date.now(),
604
- elapsedMs: event.elapsedMs
605
- });
606
- state.currentExecutingStepId = stepId;
607
- state.lastEventType = eventType;
608
- break;
609
- }
610
- case "USER_ACTION_SUCCESS": {
611
- const displayMessage = getUserActionDisplayMessage(eventType, event.message);
612
- appendThinkingText(state, "\n\u2713 " + displayMessage);
613
610
  completeLastInProgressStep(state.steps);
614
- state.userActionRequest = void 0;
615
- state.userActionPending = false;
616
- state.userActionResult = "approved";
611
+ const verificationType = event.verificationType === "ALPHANUMERIC_CODE" || event.verificationType === "NUMERIC_CODE" ? event.verificationType : void 0;
612
+ const request = {
613
+ userActionId,
614
+ kind,
615
+ rawAction,
616
+ subAction: typeof event.subAction === "string" ? event.subAction : void 0,
617
+ verificationType,
618
+ expirySeconds: typeof event.expirySeconds === "number" ? event.expirySeconds : void 0,
619
+ message: promptMessage || void 0,
620
+ requestedSchema: schema,
621
+ metadata: event.metadata,
622
+ toolCallId: typeof event.toolCallId === "string" ? event.toolCallId : void 0,
623
+ executionId: state.executionId,
624
+ sessionId: state.sessionId
625
+ };
626
+ upsertUserAction(state, request);
627
+ state.lastUserAction = request;
628
+ const header = userActionHeader(kind);
629
+ if (promptMessage) addThinkingLine(state, header, promptMessage);
630
+ else addThinkingHeader(state, header);
617
631
  const stepId = `step-${state.stepCounter++}`;
618
632
  state.steps.push({
619
633
  id: stepId,
620
634
  eventType,
621
- message: displayMessage,
622
- status: "completed",
623
- timestamp: Date.now(),
624
- elapsedMs: event.elapsedMs
625
- });
626
- state.lastEventType = eventType;
627
- break;
628
- }
629
- case "USER_ACTION_INVALID": {
630
- const displayMessage = getUserActionDisplayMessage(eventType, event.message);
631
- appendThinkingText(state, "\n\u2717 " + displayMessage);
632
- completeLastInProgressStep(state.steps);
633
- const errorStepId = `step-${state.stepCounter++}`;
634
- state.steps.push({
635
- id: errorStepId,
636
- eventType,
637
- message: displayMessage,
638
- status: "error",
639
- timestamp: Date.now(),
640
- elapsedMs: event.elapsedMs
641
- });
642
- const retryStepId = `step-${state.stepCounter++}`;
643
- state.steps.push({
644
- id: retryStepId,
645
- eventType: "USER_ACTION_REQUIRED",
646
- message: "Waiting for verification...",
635
+ message: promptMessage || (kind === "verification" ? "Waiting for verification..." : "Waiting for your input..."),
647
636
  status: "in_progress",
648
- timestamp: Date.now()
649
- });
650
- state.currentExecutingStepId = retryStepId;
651
- state.lastEventType = eventType;
652
- break;
653
- }
654
- case "USER_ACTION_REJECTED": {
655
- const displayMessage = getUserActionDisplayMessage(eventType, event.message);
656
- appendThinkingText(state, "\n" + displayMessage);
657
- completeLastInProgressStep(state.steps);
658
- state.userActionRequest = void 0;
659
- state.userActionPending = false;
660
- state.userActionResult = "rejected";
661
- const stepId = `step-${state.stepCounter++}`;
662
- state.steps.push({
663
- id: stepId,
664
- eventType,
665
- message: displayMessage,
666
- status: "completed",
667
- timestamp: Date.now(),
668
- elapsedMs: event.elapsedMs
669
- });
670
- state.lastEventType = eventType;
671
- break;
672
- }
673
- case "USER_ACTION_EXPIRED": {
674
- const displayMessage = getUserActionDisplayMessage(eventType, event.message);
675
- appendThinkingText(state, "\n\u2717 " + displayMessage);
676
- completeLastInProgressStep(state.steps);
677
- state.userActionRequest = void 0;
678
- state.userActionPending = false;
679
- const stepId = `step-${state.stepCounter++}`;
680
- state.steps.push({
681
- id: stepId,
682
- eventType,
683
- message: displayMessage,
684
- status: "error",
685
- timestamp: Date.now(),
686
- elapsedMs: event.elapsedMs
687
- });
688
- state.lastEventType = eventType;
689
- break;
690
- }
691
- case "USER_ACTION_RESENT": {
692
- const displayMessage = getUserActionDisplayMessage(eventType, event.message);
693
- appendThinkingText(state, "\n" + displayMessage);
694
- const stepId = `step-${state.stepCounter++}`;
695
- state.steps.push({
696
- id: stepId,
697
- eventType,
698
- message: displayMessage,
699
- status: "completed",
700
637
  timestamp: Date.now(),
701
638
  elapsedMs: event.elapsedMs
702
639
  });
640
+ state.currentExecutingStepId = stepId;
703
641
  state.lastEventType = eventType;
704
642
  break;
705
643
  }
706
- case "USER_ACTION_FAILED": {
707
- const displayMessage = getUserActionDisplayMessage(
708
- eventType,
709
- event.message || event.errorMessage
710
- );
711
- appendThinkingText(state, "\n\u2717 " + displayMessage);
712
- completeLastInProgressStep(state.steps);
713
- state.userActionRequest = void 0;
714
- state.userActionPending = false;
715
- const stepId = `step-${state.stepCounter++}`;
716
- state.steps.push({
717
- id: stepId,
718
- eventType,
719
- message: displayMessage,
720
- status: "error",
721
- timestamp: Date.now(),
722
- elapsedMs: event.elapsedMs
723
- });
644
+ case "USER_NOTIFICATION": {
645
+ const noteMessage = typeof event.message === "string" && event.message.trim() || message || "";
646
+ const userActionId = typeof event.userActionId === "string" ? event.userActionId : "";
647
+ if (noteMessage || userActionId) {
648
+ const notification = {
649
+ id: userActionId || `note-${state.stepCounter++}`,
650
+ message: noteMessage
651
+ };
652
+ state.notifications.push(notification);
653
+ state.lastNotification = notification;
654
+ if (noteMessage) addThinkingDetail(state, noteMessage);
655
+ }
724
656
  state.lastEventType = eventType;
725
657
  break;
726
658
  }
@@ -914,21 +846,9 @@ function buildFormattedThinking(steps, allThinkingText) {
914
846
  if (step.message) parts.push(step.message);
915
847
  break;
916
848
  case "USER_ACTION_REQUIRED":
917
- parts.push("**Verification Required**");
849
+ parts.push("**Action required**");
918
850
  if (step.message) parts.push(step.message);
919
851
  break;
920
- case "USER_ACTION_SUCCESS":
921
- parts.push(`\u2713 ${step.message || "Verification successful"}`);
922
- break;
923
- case "USER_ACTION_REJECTED":
924
- parts.push(`\u2717 ${step.message || "Verification rejected"}`);
925
- break;
926
- case "USER_ACTION_EXPIRED":
927
- parts.push(`\u2717 ${step.message || "Verification expired"}`);
928
- break;
929
- case "USER_ACTION_FAILED":
930
- parts.push(`\u2717 ${step.message || "Verification failed"}`);
931
- break;
932
852
  }
933
853
  }
934
854
  return parts.length > 0 ? parts.join("\n") : allThinkingText;
@@ -1024,6 +944,14 @@ function buildRequestHeaders(config) {
1024
944
  }
1025
945
 
1026
946
  // src/utils/userActionClient.ts
947
+ var UserActionStaleError = class extends Error {
948
+ constructor(userActionId, message = "User action is no longer actionable") {
949
+ super(message);
950
+ __publicField(this, "userActionId");
951
+ this.name = "UserActionStaleError";
952
+ this.userActionId = userActionId;
953
+ }
954
+ };
1027
955
  async function sendUserActionRequest(config, userActionId, action, data) {
1028
956
  const url = buildUserActionUrl(config, userActionId, action);
1029
957
  const baseHeaders = buildRequestHeaders(config);
@@ -1034,14 +962,17 @@ async function sendUserActionRequest(config, userActionId, action, data) {
1034
962
  headers,
1035
963
  body: hasBody ? JSON.stringify(data) : void 0
1036
964
  });
965
+ if (response.status === 404) {
966
+ throw new UserActionStaleError(userActionId);
967
+ }
1037
968
  if (!response.ok) {
1038
969
  const errorText = await response.text();
1039
970
  throw new Error(`HTTP ${response.status}: ${errorText}`);
1040
971
  }
1041
972
  return await response.json();
1042
973
  }
1043
- async function submitUserAction(config, userActionId, data) {
1044
- return sendUserActionRequest(config, userActionId, "submit", data);
974
+ async function submitUserAction(config, userActionId, content) {
975
+ return sendUserActionRequest(config, userActionId, "submit", content ?? {});
1045
976
  }
1046
977
  async function cancelUserAction(config, userActionId) {
1047
978
  return sendUserActionRequest(config, userActionId, "cancel");
@@ -1119,6 +1050,13 @@ var activeStreamStore = {
1119
1050
  streams.get(key)?.listeners.delete(listener);
1120
1051
  };
1121
1052
  },
1053
+ // Rename an entry — used when the server assigns a new session ID mid-stream
1054
+ rename(oldKey, newKey) {
1055
+ const entry = streams.get(oldKey);
1056
+ if (!entry) return;
1057
+ streams.set(newKey, entry);
1058
+ streams.delete(oldKey);
1059
+ },
1122
1060
  // Explicit user cancel — aborts the controller and removes the entry
1123
1061
  abort(key) {
1124
1062
  const entry = streams.get(key);
@@ -1215,15 +1153,11 @@ function useStreamManagerV2(config, callbacks, setMessages, setIsWaitingForRespo
1215
1153
  onEvent: (event) => {
1216
1154
  if (abortController.signal.aborted) return;
1217
1155
  processStreamEventV2(event, state);
1218
- const eventType = event.eventType;
1219
- if (eventType === "USER_ACTION_REQUIRED" && state.userActionRequest) {
1220
- callbacksRef.current.onUserActionRequired?.(state.userActionRequest);
1221
- } else if (typeof eventType === "string" && eventType.startsWith("USER_ACTION_") && eventType !== "USER_ACTION_REQUIRED") {
1222
- const msg = getUserActionDisplayMessage(
1223
- eventType,
1224
- event.message?.trim() || event.errorMessage?.trim() || getEventMessage(event)
1225
- );
1226
- callbacksRef.current.onUserActionEvent?.(eventType, msg);
1156
+ if (state.lastUserAction) {
1157
+ callbacksRef.current.onUserActionRequired?.(state.lastUserAction);
1158
+ }
1159
+ if (state.lastNotification) {
1160
+ callbacksRef.current.onUserNotification?.(state.lastNotification);
1227
1161
  }
1228
1162
  const activeStep = state.steps.find((s) => s.id === state.currentExecutingStepId);
1229
1163
  const lastInProgressStep = [...state.steps].reverse().find((s) => s.status === "in_progress");
@@ -1264,7 +1198,6 @@ function useStreamManagerV2(config, callbacks, setMessages, setIsWaitingForRespo
1264
1198
  currentExecutingStepId: state.currentExecutingStepId,
1265
1199
  executionId: state.executionId,
1266
1200
  sessionId: state.sessionId,
1267
- userActionResult: state.userActionResult,
1268
1201
  isCancelled: false
1269
1202
  });
1270
1203
  }
@@ -1275,14 +1208,6 @@ function useStreamManagerV2(config, callbacks, setMessages, setIsWaitingForRespo
1275
1208
  if (error.name !== "AbortError") {
1276
1209
  callbacksRef.current.onError?.(error);
1277
1210
  }
1278
- if (state.userActionPending) {
1279
- state.userActionPending = false;
1280
- state.userActionRequest = void 0;
1281
- callbacksRef.current.onUserActionEvent?.(
1282
- "USER_ACTION_FAILED",
1283
- "Connection lost. Please try again."
1284
- );
1285
- }
1286
1211
  const isAborted = error.name === "AbortError";
1287
1212
  setMessages(
1288
1213
  (prev) => prev.map(
@@ -1311,14 +1236,6 @@ function useStreamManagerV2(config, callbacks, setMessages, setIsWaitingForRespo
1311
1236
  setIsWaitingForResponse(false);
1312
1237
  callbacksRef.current.onStatusMessage?.(null);
1313
1238
  callbacksRef.current.onStepsUpdate?.([]);
1314
- if (state.userActionPending) {
1315
- state.userActionPending = false;
1316
- state.userActionRequest = void 0;
1317
- callbacksRef.current.onUserActionEvent?.(
1318
- "USER_ACTION_FAILED",
1319
- "Verification could not be completed."
1320
- );
1321
- }
1322
1239
  if (state.sessionId && state.sessionId !== sessionId) {
1323
1240
  callbacksRef.current.onSessionIdChange?.(state.sessionId);
1324
1241
  }
@@ -1342,7 +1259,6 @@ function useStreamManagerV2(config, callbacks, setMessages, setIsWaitingForRespo
1342
1259
  steps: state.hasError ? [] : [...state.steps],
1343
1260
  isCancelled: false,
1344
1261
  currentExecutingStepId: void 0,
1345
- userActionResult: state.userActionResult,
1346
1262
  formattedThinkingText: state.hasError ? void 0 : state.formattedThinkingText || void 0,
1347
1263
  isResolvingImages: needsImageResolve,
1348
1264
  totalElapsedMs: state.hasError ? void 0 : state.totalElapsedMs
@@ -1385,14 +1301,6 @@ function useStreamManagerV2(config, callbacks, setMessages, setIsWaitingForRespo
1385
1301
  if (error.name !== "AbortError") {
1386
1302
  callbacksRef.current.onError?.(error);
1387
1303
  }
1388
- if (state.userActionPending) {
1389
- state.userActionPending = false;
1390
- state.userActionRequest = void 0;
1391
- callbacksRef.current.onUserActionEvent?.(
1392
- "USER_ACTION_FAILED",
1393
- "Connection lost. Please try again."
1394
- );
1395
- }
1396
1304
  const isAborted = error.name === "AbortError";
1397
1305
  setMessages(
1398
1306
  (prev) => prev.map(
@@ -1431,8 +1339,23 @@ function useStreamManagerV2(config, callbacks, setMessages, setIsWaitingForRespo
1431
1339
  }
1432
1340
 
1433
1341
  // src/hooks/useChatV2.ts
1342
+ var EMPTY_USER_ACTION_STATE = { prompts: [], notifications: [] };
1343
+ function upsertPrompt(prompts, req) {
1344
+ const active = { ...req, status: "pending" };
1345
+ const idx = prompts.findIndex(
1346
+ (p) => req.toolCallId ? p.toolCallId === req.toolCallId : p.userActionId === req.userActionId
1347
+ );
1348
+ if (idx >= 0) {
1349
+ const next = prompts.slice();
1350
+ next[idx] = active;
1351
+ return next;
1352
+ }
1353
+ return [...prompts, active];
1354
+ }
1434
1355
  function getStoredOrInitialMessages(config) {
1435
1356
  if (!config.userId) return config.initialMessages ?? [];
1357
+ const activeStream = activeStreamStore.get(config.userId);
1358
+ if (activeStream) return activeStream.messages;
1436
1359
  const stored = chatStore.get(config.userId);
1437
1360
  if (stored.length > 0) return stored;
1438
1361
  if (config.initialMessages?.length) {
@@ -1446,11 +1369,16 @@ function getSessionIdFromMessages(messages) {
1446
1369
  }
1447
1370
  function useChatV2(config, callbacks = {}) {
1448
1371
  const [messages, setMessages] = react.useState(() => getStoredOrInitialMessages(config));
1449
- const [isWaitingForResponse, setIsWaitingForResponse] = react.useState(false);
1372
+ const [isWaitingForResponse, setIsWaitingForResponse] = react.useState(() => {
1373
+ if (!config.userId) return false;
1374
+ return activeStreamStore.get(config.userId)?.isWaiting ?? false;
1375
+ });
1450
1376
  const sessionIdRef = react.useRef(
1451
1377
  getSessionIdFromMessages(getStoredOrInitialMessages(config)) ?? config.initialSessionId ?? void 0
1452
1378
  );
1453
1379
  const prevUserIdRef = react.useRef(config.userId);
1380
+ const streamUserIdRef = react.useRef(void 0);
1381
+ const subscriptionPrevUserIdRef = react.useRef(config.userId);
1454
1382
  const callbacksRef = react.useRef(callbacks);
1455
1383
  callbacksRef.current = callbacks;
1456
1384
  const configRef = react.useRef(config);
@@ -1459,33 +1387,35 @@ function useChatV2(config, callbacks = {}) {
1459
1387
  messagesRef.current = messages;
1460
1388
  const storeAwareSetMessages = react.useCallback(
1461
1389
  (updater) => {
1462
- const { userId } = configRef.current;
1463
- if (userId && activeStreamStore.has(userId)) {
1464
- activeStreamStore.applyMessages(userId, updater);
1390
+ const streamUserId = streamUserIdRef.current;
1391
+ const currentUserId = configRef.current.userId;
1392
+ const storeKey = streamUserId ?? currentUserId;
1393
+ if (storeKey && activeStreamStore.has(storeKey)) {
1394
+ activeStreamStore.applyMessages(storeKey, updater);
1395
+ }
1396
+ if (!streamUserId || streamUserId === currentUserId) {
1397
+ setMessages(updater);
1465
1398
  }
1466
- setMessages(updater);
1467
1399
  },
1468
1400
  // eslint-disable-next-line react-hooks/exhaustive-deps
1469
1401
  []
1470
1402
  );
1471
1403
  const storeAwareSetIsWaiting = react.useCallback(
1472
1404
  (waiting) => {
1473
- const { userId } = configRef.current;
1474
- if (userId && activeStreamStore.has(userId)) {
1475
- activeStreamStore.setWaiting(userId, waiting);
1405
+ const streamUserId = streamUserIdRef.current;
1406
+ const currentUserId = configRef.current.userId;
1407
+ const storeKey = streamUserId ?? currentUserId;
1408
+ if (storeKey && activeStreamStore.has(storeKey)) {
1409
+ activeStreamStore.setWaiting(storeKey, waiting);
1410
+ }
1411
+ if (!streamUserId || streamUserId === currentUserId) {
1412
+ setIsWaitingForResponse(waiting);
1476
1413
  }
1477
- setIsWaitingForResponse(waiting);
1478
1414
  },
1479
1415
  // eslint-disable-next-line react-hooks/exhaustive-deps
1480
1416
  []
1481
1417
  );
1482
- const [userActionState, setUserActionState] = react.useState({
1483
- request: null,
1484
- result: null,
1485
- clearOtpTrigger: 0
1486
- });
1487
- const userActionStateRef = react.useRef(userActionState);
1488
- userActionStateRef.current = userActionState;
1418
+ const [userActionState, setUserActionState] = react.useState(EMPTY_USER_ACTION_STATE);
1489
1419
  const wrappedCallbacks = react.useMemo(() => ({
1490
1420
  ...callbacksRef.current,
1491
1421
  onMessageSent: (message) => callbacksRef.current.onMessageSent?.(message),
@@ -1495,26 +1425,17 @@ function useChatV2(config, callbacks = {}) {
1495
1425
  onExecutionTraceClick: (data) => callbacksRef.current.onExecutionTraceClick?.(data),
1496
1426
  onSessionIdChange: (sessionId) => callbacksRef.current.onSessionIdChange?.(sessionId),
1497
1427
  onUserActionRequired: (request) => {
1498
- setUserActionState((prev) => ({ ...prev, request, result: null }));
1428
+ setUserActionState((prev) => ({
1429
+ ...prev,
1430
+ prompts: upsertPrompt(prev.prompts, request)
1431
+ }));
1499
1432
  callbacksRef.current.onUserActionRequired?.(request);
1500
1433
  },
1501
- onUserActionEvent: (eventType, message) => {
1502
- switch (eventType) {
1503
- case "USER_ACTION_SUCCESS":
1504
- setUserActionState((prev) => ({ ...prev, request: null, result: "approved" }));
1505
- break;
1506
- case "USER_ACTION_REJECTED":
1507
- setUserActionState((prev) => ({ ...prev, request: null, result: "rejected" }));
1508
- break;
1509
- case "USER_ACTION_EXPIRED":
1510
- case "USER_ACTION_FAILED":
1511
- setUserActionState((prev) => ({ ...prev, request: null }));
1512
- break;
1513
- case "USER_ACTION_INVALID":
1514
- setUserActionState((prev) => ({ ...prev, clearOtpTrigger: prev.clearOtpTrigger + 1 }));
1515
- break;
1516
- }
1517
- callbacksRef.current.onUserActionEvent?.(eventType, message);
1434
+ onUserNotification: (notification) => {
1435
+ setUserActionState(
1436
+ (prev) => prev.notifications.some((n) => n.id === notification.id) ? prev : { ...prev, notifications: [...prev.notifications, notification] }
1437
+ );
1438
+ callbacksRef.current.onUserNotification?.(notification);
1518
1439
  }
1519
1440
  // eslint-disable-next-line react-hooks/exhaustive-deps
1520
1441
  }), []);
@@ -1562,6 +1483,7 @@ function useChatV2(config, callbacks = {}) {
1562
1483
  const abortController = new AbortController();
1563
1484
  const { userId } = configRef.current;
1564
1485
  if (userId) {
1486
+ streamUserIdRef.current = userId;
1565
1487
  const initialMessages = [...messagesRef.current, userMsg, streamingMsg];
1566
1488
  activeStreamStore.start(userId, abortController, initialMessages);
1567
1489
  }
@@ -1572,9 +1494,11 @@ function useChatV2(config, callbacks = {}) {
1572
1494
  abortController,
1573
1495
  options
1574
1496
  );
1575
- if (userId) {
1576
- activeStreamStore.complete(userId);
1497
+ const finalStreamUserId = streamUserIdRef.current ?? userId;
1498
+ if (finalStreamUserId) {
1499
+ activeStreamStore.complete(finalStreamUserId);
1577
1500
  }
1501
+ streamUserIdRef.current = void 0;
1578
1502
  if (!abortController.signal.aborted && newSessionId && newSessionId !== sessionIdRef.current) {
1579
1503
  sessionIdRef.current = newSessionId;
1580
1504
  }
@@ -1591,12 +1515,14 @@ function useChatV2(config, callbacks = {}) {
1591
1515
  setMessages((prev) => [...msgs, ...prev]);
1592
1516
  }, []);
1593
1517
  const cancelStream = react.useCallback(() => {
1594
- if (configRef.current.userId) {
1595
- activeStreamStore.abort(configRef.current.userId);
1518
+ const streamUserId = streamUserIdRef.current ?? configRef.current.userId;
1519
+ if (streamUserId) {
1520
+ activeStreamStore.abort(streamUserId);
1596
1521
  }
1522
+ streamUserIdRef.current = void 0;
1597
1523
  cancelStreamManager();
1598
1524
  setIsWaitingForResponse(false);
1599
- setUserActionState((prev) => ({ ...prev, request: null, result: null }));
1525
+ setUserActionState((prev) => ({ ...prev, prompts: [] }));
1600
1526
  setMessages(
1601
1527
  (prev) => prev.map((msg) => {
1602
1528
  if (msg.isStreaming) {
@@ -1613,15 +1539,19 @@ function useChatV2(config, callbacks = {}) {
1613
1539
  );
1614
1540
  }, [cancelStreamManager]);
1615
1541
  const resetSession = react.useCallback(() => {
1542
+ const streamUserId = streamUserIdRef.current ?? configRef.current.userId;
1543
+ if (streamUserId) {
1544
+ activeStreamStore.abort(streamUserId);
1545
+ }
1616
1546
  if (configRef.current.userId) {
1617
- activeStreamStore.abort(configRef.current.userId);
1618
1547
  chatStore.delete(configRef.current.userId);
1619
1548
  }
1549
+ streamUserIdRef.current = void 0;
1620
1550
  setMessages([]);
1621
1551
  sessionIdRef.current = void 0;
1622
1552
  abortControllerRef.current?.abort();
1623
1553
  setIsWaitingForResponse(false);
1624
- setUserActionState({ request: null, result: null, clearOtpTrigger: 0 });
1554
+ setUserActionState(EMPTY_USER_ACTION_STATE);
1625
1555
  }, []);
1626
1556
  const getSessionId = react.useCallback(() => {
1627
1557
  return sessionIdRef.current;
@@ -1629,59 +1559,91 @@ function useChatV2(config, callbacks = {}) {
1629
1559
  const getMessages = react.useCallback(() => {
1630
1560
  return messages;
1631
1561
  }, [messages]);
1632
- const approveUserAction = react.useCallback(
1633
- async (otp) => {
1634
- const request = userActionStateRef.current.request;
1635
- if (!request) return;
1562
+ const setPromptStatus = react.useCallback(
1563
+ (userActionId, status) => {
1564
+ setUserActionState((prev) => ({
1565
+ ...prev,
1566
+ prompts: prev.prompts.map(
1567
+ (p) => p.userActionId === userActionId ? { ...p, status } : p
1568
+ )
1569
+ }));
1570
+ },
1571
+ []
1572
+ );
1573
+ const removePrompt = react.useCallback((userActionId) => {
1574
+ setUserActionState((prev) => ({
1575
+ ...prev,
1576
+ prompts: prev.prompts.filter((p) => p.userActionId !== userActionId)
1577
+ }));
1578
+ }, []);
1579
+ const submitUserAction2 = react.useCallback(
1580
+ async (userActionId, content) => {
1581
+ setPromptStatus(userActionId, "submitting");
1636
1582
  try {
1637
- await submitUserAction(configRef.current, request.userActionId, { otp });
1583
+ await submitUserAction(configRef.current, userActionId, content);
1584
+ removePrompt(userActionId);
1638
1585
  } catch (error) {
1639
- setUserActionState((prev) => ({
1640
- ...prev,
1641
- clearOtpTrigger: prev.clearOtpTrigger + 1
1642
- }));
1586
+ if (error instanceof UserActionStaleError) {
1587
+ setPromptStatus(userActionId, "stale");
1588
+ return;
1589
+ }
1590
+ setPromptStatus(userActionId, "pending");
1643
1591
  callbacksRef.current.onError?.(error);
1644
1592
  throw error;
1645
1593
  }
1646
1594
  },
1647
- []
1595
+ [removePrompt, setPromptStatus]
1648
1596
  );
1649
- const rejectUserAction = react.useCallback(async () => {
1650
- const request = userActionStateRef.current.request;
1651
- if (!request) return;
1652
- try {
1653
- setMessages((prev) => {
1654
- let lastStreamingIdx = -1;
1655
- for (let i = prev.length - 1; i >= 0; i--) {
1656
- if (prev[i].role === "assistant" && prev[i].isStreaming) {
1657
- lastStreamingIdx = i;
1658
- break;
1659
- }
1597
+ const cancelUserAction2 = react.useCallback(
1598
+ async (userActionId) => {
1599
+ setPromptStatus(userActionId, "submitting");
1600
+ try {
1601
+ await cancelUserAction(configRef.current, userActionId);
1602
+ removePrompt(userActionId);
1603
+ } catch (error) {
1604
+ if (error instanceof UserActionStaleError) {
1605
+ removePrompt(userActionId);
1606
+ return;
1660
1607
  }
1661
- if (lastStreamingIdx === -1) return prev;
1662
- return prev.map(
1663
- (msg, i) => i === lastStreamingIdx ? { ...msg, currentMessage: "Rejecting..." } : msg
1664
- );
1665
- });
1666
- await cancelUserAction(configRef.current, request.userActionId);
1667
- } catch (error) {
1668
- callbacksRef.current.onError?.(error);
1669
- throw error;
1670
- }
1671
- }, []);
1672
- const resendOtp = react.useCallback(async () => {
1673
- const request = userActionStateRef.current.request;
1674
- if (!request) return;
1675
- try {
1676
- await resendUserAction(configRef.current, request.userActionId);
1677
- } catch (error) {
1678
- callbacksRef.current.onError?.(error);
1679
- throw error;
1680
- }
1608
+ setPromptStatus(userActionId, "pending");
1609
+ callbacksRef.current.onError?.(error);
1610
+ throw error;
1611
+ }
1612
+ },
1613
+ [removePrompt, setPromptStatus]
1614
+ );
1615
+ const resendUserAction2 = react.useCallback(
1616
+ async (userActionId) => {
1617
+ setPromptStatus(userActionId, "submitting");
1618
+ try {
1619
+ await resendUserAction(configRef.current, userActionId);
1620
+ setPromptStatus(userActionId, "pending");
1621
+ } catch (error) {
1622
+ if (error instanceof UserActionStaleError) {
1623
+ setPromptStatus(userActionId, "stale");
1624
+ return;
1625
+ }
1626
+ setPromptStatus(userActionId, "pending");
1627
+ callbacksRef.current.onError?.(error);
1628
+ throw error;
1629
+ }
1630
+ },
1631
+ [setPromptStatus]
1632
+ );
1633
+ const dismissNotification = react.useCallback((id) => {
1634
+ setUserActionState((prev) => ({
1635
+ ...prev,
1636
+ notifications: prev.notifications.filter((n) => n.id !== id)
1637
+ }));
1681
1638
  }, []);
1682
1639
  react.useEffect(() => {
1640
+ const prevSubscriptionUserId = subscriptionPrevUserIdRef.current;
1641
+ subscriptionPrevUserIdRef.current = config.userId;
1683
1642
  const { userId } = config;
1684
1643
  if (!userId) return;
1644
+ if (prevSubscriptionUserId && prevSubscriptionUserId !== userId && streamUserIdRef.current === prevSubscriptionUserId && !activeStreamStore.has(prevSubscriptionUserId) && activeStreamStore.has(userId)) {
1645
+ streamUserIdRef.current = userId;
1646
+ }
1685
1647
  const unsubscribe = activeStreamStore.subscribe(userId, (msgs, isWaiting) => {
1686
1648
  setMessages(msgs);
1687
1649
  setIsWaitingForResponse(isWaiting);
@@ -1695,6 +1657,7 @@ function useChatV2(config, callbacks = {}) {
1695
1657
  }, [config.userId]);
1696
1658
  react.useEffect(() => {
1697
1659
  if (!config.userId) return;
1660
+ if (prevUserIdRef.current !== config.userId) return;
1698
1661
  const toSave = messages.filter((m) => !m.isStreaming);
1699
1662
  if (toSave.length > 0) {
1700
1663
  chatStore.set(config.userId, toSave);
@@ -1716,7 +1679,7 @@ function useChatV2(config, callbacks = {}) {
1716
1679
  setMessages([]);
1717
1680
  sessionIdRef.current = void 0;
1718
1681
  setIsWaitingForResponse(false);
1719
- setUserActionState({ request: null, result: null, clearOtpTrigger: 0 });
1682
+ setUserActionState(EMPTY_USER_ACTION_STATE);
1720
1683
  } else if (config.userId) {
1721
1684
  const active = activeStreamStore.get(config.userId);
1722
1685
  if (active) {
@@ -1743,9 +1706,10 @@ function useChatV2(config, callbacks = {}) {
1743
1706
  isWaitingForResponse,
1744
1707
  sessionId: sessionIdRef.current,
1745
1708
  userActionState,
1746
- approveUserAction,
1747
- rejectUserAction,
1748
- resendOtp
1709
+ submitUserAction: submitUserAction2,
1710
+ cancelUserAction: cancelUserAction2,
1711
+ resendUserAction: resendUserAction2,
1712
+ dismissNotification
1749
1713
  };
1750
1714
  }
1751
1715
  function getSpeechRecognition() {
@@ -1964,15 +1928,151 @@ function useVoice(config = {}, callbacks = {}) {
1964
1928
  };
1965
1929
  }
1966
1930
 
1931
+ // src/utils/jsonSchemaForm.ts
1932
+ function classifyField(field) {
1933
+ if (!field) return "text";
1934
+ if (Array.isArray(field.oneOf) && field.oneOf.length > 0) return "select";
1935
+ switch (field.type) {
1936
+ case "boolean":
1937
+ return "boolean";
1938
+ case "integer":
1939
+ return "integer";
1940
+ case "number":
1941
+ return "decimal";
1942
+ case "string":
1943
+ return "text";
1944
+ default:
1945
+ return "text";
1946
+ }
1947
+ }
1948
+ function isNestedOrUnsupported(field) {
1949
+ if (!field) return false;
1950
+ if (field.type === "object" || field.type === "array") return true;
1951
+ if ("properties" in field && field.properties != null) return true;
1952
+ if ("items" in field && field.items != null) return true;
1953
+ return false;
1954
+ }
1955
+ function getOptions(field) {
1956
+ if (!field || !Array.isArray(field.oneOf)) return [];
1957
+ return field.oneOf.filter(
1958
+ (o) => !!o && typeof o === "object" && typeof o.const === "string"
1959
+ );
1960
+ }
1961
+ function isRequired(schema, key) {
1962
+ return Array.isArray(schema?.required) && schema.required.includes(key);
1963
+ }
1964
+ function coerceValue(field, raw) {
1965
+ const widget = classifyField(field);
1966
+ if (widget === "boolean") {
1967
+ if (typeof raw === "boolean") return raw;
1968
+ if (raw === "true") return true;
1969
+ if (raw === "false") return false;
1970
+ return Boolean(raw);
1971
+ }
1972
+ if (widget === "integer" || widget === "decimal") {
1973
+ if (raw === "" || raw == null) return void 0;
1974
+ const num = typeof raw === "number" ? raw : Number(String(raw).trim());
1975
+ if (Number.isNaN(num)) return void 0;
1976
+ return widget === "integer" ? Math.trunc(num) : num;
1977
+ }
1978
+ if (raw == null) return void 0;
1979
+ const str = String(raw);
1980
+ return str === "" ? void 0 : str;
1981
+ }
1982
+ function defaultValueFor(field) {
1983
+ if (!field || field.default === void 0) {
1984
+ return classifyField(field) === "boolean" ? false : "";
1985
+ }
1986
+ return field.default;
1987
+ }
1988
+ function validateField(field, value, required) {
1989
+ const widget = classifyField(field);
1990
+ const label = field?.title || "This field";
1991
+ const isEmpty = value === void 0 || value === null || typeof value === "string" && value.trim() === "";
1992
+ if (isEmpty) {
1993
+ if (required && widget !== "boolean") return `${label} is required.`;
1994
+ return null;
1995
+ }
1996
+ if (widget === "integer" || widget === "decimal") {
1997
+ const num = typeof value === "number" ? value : Number(value);
1998
+ if (Number.isNaN(num)) return `${label} must be a number.`;
1999
+ if (widget === "integer" && !Number.isInteger(num)) {
2000
+ return `${label} must be a whole number.`;
2001
+ }
2002
+ if (typeof field?.minimum === "number" && num < field.minimum) {
2003
+ return `${label} must be at least ${field.minimum}.`;
2004
+ }
2005
+ if (typeof field?.maximum === "number" && num > field.maximum) {
2006
+ return `${label} must be at most ${field.maximum}.`;
2007
+ }
2008
+ return null;
2009
+ }
2010
+ if (widget === "select") {
2011
+ const allowed = getOptions(field).map((o) => o.const);
2012
+ if (allowed.length > 0 && !allowed.includes(String(value))) {
2013
+ return `${label} has an invalid selection.`;
2014
+ }
2015
+ return null;
2016
+ }
2017
+ const str = String(value);
2018
+ if (typeof field?.minLength === "number" && str.length < field.minLength) {
2019
+ return `${label} must be at least ${field.minLength} characters.`;
2020
+ }
2021
+ if (typeof field?.maxLength === "number" && str.length > field.maxLength) {
2022
+ return `${label} must be at most ${field.maxLength} characters.`;
2023
+ }
2024
+ return null;
2025
+ }
2026
+ function renderableFields(schema) {
2027
+ const props = schema?.properties;
2028
+ if (!props) return [];
2029
+ return Object.entries(props).filter(([, field]) => !isNestedOrUnsupported(field));
2030
+ }
2031
+ function validateForm(schema, values) {
2032
+ const errors = {};
2033
+ for (const [key, field] of renderableFields(schema)) {
2034
+ const coerced = coerceValue(field, values[key]);
2035
+ const err = validateField(field, coerced, isRequired(schema, key));
2036
+ if (err) errors[key] = err;
2037
+ }
2038
+ return errors;
2039
+ }
2040
+ function buildContent(schema, values) {
2041
+ const content = {};
2042
+ for (const [key, field] of renderableFields(schema)) {
2043
+ const coerced = coerceValue(field, values[key]);
2044
+ if (coerced !== void 0) content[key] = coerced;
2045
+ }
2046
+ return content;
2047
+ }
2048
+
2049
+ // src/index.ts
2050
+ function migrateActiveStream(oldUserId, newUserId) {
2051
+ activeStreamStore.rename(oldUserId, newUserId);
2052
+ }
2053
+
2054
+ exports.UserActionStaleError = UserActionStaleError;
2055
+ exports.buildContent = buildContent;
1967
2056
  exports.buildFormattedThinking = buildFormattedThinking;
1968
2057
  exports.cancelUserAction = cancelUserAction;
2058
+ exports.classifyField = classifyField;
2059
+ exports.classifyUserActionKind = classifyUserActionKind;
2060
+ exports.coerceValue = coerceValue;
1969
2061
  exports.createInitialV2State = createInitialV2State;
2062
+ exports.defaultValueFor = defaultValueFor;
1970
2063
  exports.generateId = generateId;
2064
+ exports.getOptions = getOptions;
2065
+ exports.isNestedOrUnsupported = isNestedOrUnsupported;
2066
+ exports.isRequired = isRequired;
2067
+ exports.migrateActiveStream = migrateActiveStream;
1971
2068
  exports.processStreamEventV2 = processStreamEventV2;
2069
+ exports.renderableFields = renderableFields;
1972
2070
  exports.resendUserAction = resendUserAction;
1973
2071
  exports.streamWorkflowEvents = streamWorkflowEvents;
1974
2072
  exports.submitUserAction = submitUserAction;
1975
2073
  exports.useChatV2 = useChatV2;
1976
2074
  exports.useVoice = useVoice;
2075
+ exports.validateField = validateField;
2076
+ exports.validateForm = validateForm;
1977
2077
  //# sourceMappingURL=index.js.map
1978
2078
  //# sourceMappingURL=index.js.map