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