@paymanai/payman-ask-sdk 4.0.24 → 4.0.26

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,3 +1,4 @@
1
+ import * as React from 'react';
1
2
  import { createContext, forwardRef, useCallback, useRef, useState, useMemo, useEffect, useImperativeHandle, useLayoutEffect, useContext } from 'react';
2
3
  import { AnimatePresence, motion } from 'framer-motion';
3
4
  import { clsx } from 'clsx';
@@ -33,6 +34,7 @@ var chatStore = {
33
34
  memoryStore.delete(key);
34
35
  }
35
36
  };
37
+ var EMPTY_USER_ACTION_STATE = { prompts: [], notifications: [] };
36
38
  var streams = /* @__PURE__ */ new Map();
37
39
  var activeStreamStore = {
38
40
  has(key) {
@@ -41,7 +43,11 @@ var activeStreamStore = {
41
43
  get(key) {
42
44
  const entry = streams.get(key);
43
45
  if (!entry) return null;
44
- return { messages: entry.messages, isWaiting: entry.isWaiting };
46
+ return {
47
+ messages: entry.messages,
48
+ isWaiting: entry.isWaiting,
49
+ userActionState: entry.userActionState
50
+ };
45
51
  },
46
52
  // Called before startStream — registers the controller and initial messages
47
53
  start(key, abortController, initialMessages) {
@@ -49,6 +55,7 @@ var activeStreamStore = {
49
55
  streams.set(key, {
50
56
  messages: initialMessages,
51
57
  isWaiting: true,
58
+ userActionState: EMPTY_USER_ACTION_STATE,
52
59
  abortController,
53
60
  listeners: existing?.listeners ?? /* @__PURE__ */ new Set()
54
61
  });
@@ -59,20 +66,27 @@ var activeStreamStore = {
59
66
  if (!entry) return;
60
67
  const next = typeof updater === "function" ? updater(entry.messages) : updater;
61
68
  entry.messages = next;
62
- entry.listeners.forEach((l) => l(next, entry.isWaiting));
69
+ entry.listeners.forEach((l) => l(next, entry.isWaiting, entry.userActionState));
63
70
  },
64
71
  setWaiting(key, waiting) {
65
72
  const entry = streams.get(key);
66
73
  if (!entry) return;
67
74
  entry.isWaiting = waiting;
68
- entry.listeners.forEach((l) => l(entry.messages, waiting));
75
+ entry.listeners.forEach((l) => l(entry.messages, waiting, entry.userActionState));
76
+ },
77
+ applyUserActionState(key, updater) {
78
+ const entry = streams.get(key);
79
+ if (!entry) return;
80
+ const next = typeof updater === "function" ? updater(entry.userActionState) : updater;
81
+ entry.userActionState = next;
82
+ entry.listeners.forEach((l) => l(entry.messages, entry.isWaiting, next));
69
83
  },
70
84
  // Called when stream completes — persists to chatStore and cleans up
71
85
  complete(key) {
72
86
  const entry = streams.get(key);
73
87
  if (!entry) return;
74
88
  entry.isWaiting = false;
75
- entry.listeners.forEach((l) => l(entry.messages, false));
89
+ entry.listeners.forEach((l) => l(entry.messages, false, entry.userActionState));
76
90
  const toSave = entry.messages.filter((m) => !m.isStreaming);
77
91
  if (toSave.length > 0) chatStore.set(key, toSave);
78
92
  streams.delete(key);
@@ -260,6 +274,8 @@ function getEventMessage(event) {
260
274
  }
261
275
  case "RUN_IN_PROGRESS":
262
276
  return event.partialText || "Thinking...";
277
+ case "LLM_CALL_STARTED":
278
+ return event.description || "";
263
279
  case "RUN_COMPLETED":
264
280
  return "Agent run completed";
265
281
  case "RUN_FAILED":
@@ -397,7 +413,8 @@ function createInitialV2State() {
397
413
  finalData: void 0,
398
414
  steps: [],
399
415
  stepCounter: 0,
400
- currentExecutingStepId: void 0
416
+ currentExecutingStepId: void 0,
417
+ trailingActivity: false
401
418
  };
402
419
  }
403
420
  function upsertUserAction(state, req) {
@@ -419,6 +436,7 @@ function processStreamEventV2(rawEvent, state) {
419
436
  if (typeof eventType === "string" && eventType.toUpperCase() === "KEEP_ALIVE") {
420
437
  if (event.executionId) state.executionId = event.executionId;
421
438
  if (event.sessionId) state.sessionId = event.sessionId;
439
+ if (state.finalResponse) state.trailingActivity = true;
422
440
  const description = typeof event.description === "string" ? event.description : "";
423
441
  if (description) {
424
442
  for (let i = state.steps.length - 1; i >= 0; i--) {
@@ -453,7 +471,12 @@ function processStreamEventV2(rawEvent, state) {
453
471
  state.lastEventType = eventType;
454
472
  break;
455
473
  }
474
+ case "LLM_CALL_STARTED":
475
+ if (state.finalResponse) state.trailingActivity = true;
476
+ state.lastEventType = eventType;
477
+ break;
456
478
  case "INTENT_STARTED": {
479
+ if (state.finalResponse) state.trailingActivity = true;
457
480
  const stepId = `step-${state.stepCounter++}`;
458
481
  state.steps.push({
459
482
  id: stepId,
@@ -469,6 +492,7 @@ function processStreamEventV2(rawEvent, state) {
469
492
  break;
470
493
  }
471
494
  case "INTENT_COMPLETED": {
495
+ if (state.finalResponse) state.trailingActivity = true;
472
496
  const intentStep = state.steps.find((s) => s.eventType === "INTENT_STARTED" && s.status === "in_progress");
473
497
  if (intentStep) {
474
498
  intentStep.status = "completed";
@@ -500,6 +524,7 @@ function processStreamEventV2(rawEvent, state) {
500
524
  step2.status = "completed";
501
525
  }
502
526
  });
527
+ state.trailingActivity = false;
503
528
  state.lastEventType = eventType;
504
529
  break;
505
530
  }
@@ -571,6 +596,7 @@ function processStreamEventV2(rawEvent, state) {
571
596
  case "WORKFLOW_ERROR":
572
597
  state.hasError = true;
573
598
  state.errorMessage = event.errorMessage || event.message || "Workflow error";
599
+ state.trailingActivity = false;
574
600
  state.lastEventType = eventType;
575
601
  break;
576
602
  // ---- K2 pipeline stage lifecycle events ----
@@ -709,8 +735,7 @@ function buildRequestBody(config, userMessage, sessionId, options) {
709
735
  sessionAttributes,
710
736
  analysisMode: options?.analysisMode,
711
737
  locale: resolveLocale(config.locale),
712
- timezone: resolveTimezone(config.timezone),
713
- ...options?.attachments?.length ? { attachments: options.attachments } : {}
738
+ timezone: resolveTimezone(config.timezone)
714
739
  };
715
740
  }
716
741
  function resolveLocale(configured) {
@@ -755,7 +780,6 @@ function buildResolveImagesUrl(config) {
755
780
  const basePath = normalizedEndpointPath.endsWith("/stream") ? normalizedEndpointPath.slice(0, -"/stream".length) : normalizedEndpointPath;
756
781
  return `${config.api.baseUrl}${basePath}/resolve-image-urls`;
757
782
  }
758
- var NGROK_SKIP_BROWSER_WARNING = "ngrok-skip-browser-warning";
759
783
  function buildRequestHeaders(config) {
760
784
  const headers = {
761
785
  ...config.api.headers
@@ -763,9 +787,6 @@ function buildRequestHeaders(config) {
763
787
  if (config.api.authToken) {
764
788
  headers.Authorization = `Bearer ${config.api.authToken}`;
765
789
  }
766
- if (!headers[NGROK_SKIP_BROWSER_WARNING]) {
767
- headers[NGROK_SKIP_BROWSER_WARNING] = "true";
768
- }
769
790
  return headers;
770
791
  }
771
792
  var RAG_IMAGE_REGEX = /\/api\/rag\/chunks\/[^"'\s]+\/image/;
@@ -864,7 +885,9 @@ function useStreamManagerV2(config, callbacks, setMessages, setIsWaitingForRespo
864
885
  const latestUsefulStep = [...state.steps].reverse().find(
865
886
  (s) => s.message && !isBlandStatus(s.message)
866
887
  );
867
- const currentMessage = useful(activeStep?.message) ?? useful(lastInProgressStep?.message) ?? latestUsefulStep?.message ?? useful(getEventMessage(event)) ?? // Last-resort: every candidate is bland (very first event,
888
+ const eventTypeUpper = typeof event.eventType === "string" ? event.eventType.toUpperCase() : "";
889
+ const liveEventLabel = eventTypeUpper === "KEEP_ALIVE" || eventTypeUpper === "LLM_CALL_STARTED" ? useful(getEventMessage(event)) : void 0;
890
+ const currentMessage = useful(activeStep?.message) ?? useful(lastInProgressStep?.message) ?? liveEventLabel ?? latestUsefulStep?.message ?? useful(getEventMessage(event)) ?? // Last-resort: every candidate is bland (very first event,
868
891
  // nothing useful seen yet). Render the bland label so the
869
892
  // bubble isn't blank.
870
893
  activeStep?.message ?? lastInProgressStep?.message ?? getEventMessage(event);
@@ -889,6 +912,7 @@ function useStreamManagerV2(config, callbacks, setMessages, setIsWaitingForRespo
889
912
  streamingContent: state.finalResponse,
890
913
  content: "",
891
914
  currentMessage,
915
+ hasTrailingActivity: state.trailingActivity,
892
916
  streamProgress: "processing",
893
917
  isError: false,
894
918
  steps: [...state.steps],
@@ -1046,81 +1070,6 @@ function createCancelledMessageUpdate(steps, currentMessage) {
1046
1070
  currentMessage: currentMessage || "Thinking..."
1047
1071
  };
1048
1072
  }
1049
- var DEFAULT_SIGNED_URL_ENDPOINT = "/api/files/signed-url";
1050
- function fileExtension(filename) {
1051
- const ext = filename.split(".").pop()?.toLowerCase().replace(/^\./, "");
1052
- return ext && ext.length > 0 ? ext : "bin";
1053
- }
1054
- function resolveMimeType(file) {
1055
- if (file.type && file.type.trim().length > 0) return file.type;
1056
- const ext = fileExtension(file.name);
1057
- const byExt = {
1058
- pdf: "application/pdf",
1059
- png: "image/png",
1060
- jpg: "image/jpeg",
1061
- jpeg: "image/jpeg",
1062
- gif: "image/gif",
1063
- webp: "image/webp"
1064
- };
1065
- return byExt[ext] ?? "application/octet-stream";
1066
- }
1067
- function buildSignedUrlEndpoint(config, ext) {
1068
- const endpoint = config.api.signedUrlEndpoint || DEFAULT_SIGNED_URL_ENDPOINT;
1069
- const queryParams = new URLSearchParams({ extn: ext.replace(/^\./, "") });
1070
- return `${config.api.baseUrl}${endpoint}?${queryParams.toString()}`;
1071
- }
1072
- async function requestSignedUrl(config, ext, signal) {
1073
- const url = buildSignedUrlEndpoint(config, ext);
1074
- const headers = buildRequestHeaders(config);
1075
- const response = await fetch(url, {
1076
- method: "GET",
1077
- headers,
1078
- signal
1079
- });
1080
- if (!response.ok) {
1081
- const errorText = await response.text().catch(() => "");
1082
- throw new Error(
1083
- errorText ? `Failed to get upload URL (${response.status}): ${errorText}` : `Failed to get upload URL (${response.status})`
1084
- );
1085
- }
1086
- const data = await response.json();
1087
- if (!data.key || !data.url) {
1088
- throw new Error("Signed URL response missing key or url");
1089
- }
1090
- return { key: data.key, url: data.url };
1091
- }
1092
- async function uploadToSignedUrl(signedUrl, file, mimeType, signal) {
1093
- const response = await fetch(signedUrl, {
1094
- method: "PUT",
1095
- headers: {
1096
- "Content-Type": mimeType,
1097
- "x-ms-blob-type": "BlockBlob"
1098
- },
1099
- body: file,
1100
- signal
1101
- });
1102
- if (!response.ok) {
1103
- const errorText = await response.text().catch(() => "");
1104
- throw new Error(
1105
- errorText ? `Failed to upload file (${response.status}): ${errorText}` : `Failed to upload file (${response.status})`
1106
- );
1107
- }
1108
- }
1109
- async function uploadAttachment(config, file, signal) {
1110
- const ext = fileExtension(file.name);
1111
- const mimeType = resolveMimeType(file);
1112
- const { key, url } = await requestSignedUrl(config, ext, signal);
1113
- await uploadToSignedUrl(url, file, mimeType, signal);
1114
- return {
1115
- tempKey: key,
1116
- filename: file.name,
1117
- mimeType
1118
- };
1119
- }
1120
- async function uploadAttachments(config, files, signal) {
1121
- const uploads = files.map((file) => uploadAttachment(config, file, signal));
1122
- return Promise.all(uploads);
1123
- }
1124
1073
  var UserActionStaleError = class extends Error {
1125
1074
  constructor(userActionId, message = "User action is no longer actionable") {
1126
1075
  super(message);
@@ -1157,12 +1106,34 @@ async function cancelUserAction(config, userActionId) {
1157
1106
  async function resendUserAction(config, userActionId) {
1158
1107
  return sendUserActionRequest(config, userActionId, "resend");
1159
1108
  }
1160
- var EMPTY_USER_ACTION_STATE = { prompts: [], notifications: [] };
1109
+ async function expireUserAction(config, userActionId) {
1110
+ return sendUserActionRequest(config, userActionId, "expired");
1111
+ }
1112
+ function resolveUserActionExpiresAt(req, existing) {
1113
+ if (typeof req.expirySeconds !== "number" || req.expirySeconds <= 0) {
1114
+ return void 0;
1115
+ }
1116
+ if (existing?.expiresAt && existing.userActionId === req.userActionId) {
1117
+ return existing.expiresAt;
1118
+ }
1119
+ return Date.now() + Math.floor(req.expirySeconds) * 1e3;
1120
+ }
1121
+ function getUserActionSecondsLeft(prompt) {
1122
+ if (typeof prompt.expiresAt === "number" && prompt.expiresAt > 0) {
1123
+ return Math.max(0, Math.ceil((prompt.expiresAt - Date.now()) / 1e3));
1124
+ }
1125
+ if (typeof prompt.expirySeconds === "number" && prompt.expirySeconds > 0) {
1126
+ return Math.floor(prompt.expirySeconds);
1127
+ }
1128
+ return void 0;
1129
+ }
1130
+ var EMPTY_USER_ACTION_STATE2 = { prompts: [], notifications: [] };
1161
1131
  function upsertPrompt(prompts, req) {
1162
- const active = { ...req, status: "pending" };
1163
1132
  const idx = prompts.findIndex(
1164
1133
  (p) => req.toolCallId ? p.toolCallId === req.toolCallId : p.userActionId === req.userActionId
1165
1134
  );
1135
+ const expiresAt = resolveUserActionExpiresAt(req, idx >= 0 ? prompts[idx] : void 0);
1136
+ const active = { ...req, status: "pending", expiresAt };
1166
1137
  if (idx >= 0) {
1167
1138
  const next = prompts.slice();
1168
1139
  next[idx] = active;
@@ -1185,32 +1156,12 @@ function getStoredOrInitialMessages(config) {
1185
1156
  function getSessionIdFromMessages(messages) {
1186
1157
  return messages.find((message) => message.sessionId)?.sessionId;
1187
1158
  }
1188
- var IMAGE_EXTENSIONS = /* @__PURE__ */ new Set(["jpg", "jpeg", "png", "gif", "webp", "avif"]);
1189
- function attachmentKindFromFile(file) {
1190
- const ext = file.name.split(".").pop()?.toLowerCase() ?? "";
1191
- if (IMAGE_EXTENSIONS.has(ext) || file.type.startsWith("image/")) return "image";
1192
- return "file";
1193
- }
1194
- function buildMessageAttachments(files) {
1195
- return files.map((file, index) => {
1196
- const kind = attachmentKindFromFile(file);
1197
- return {
1198
- id: `att-${Date.now()}-${index}`,
1199
- filename: file.name,
1200
- mimeType: file.type || "application/octet-stream",
1201
- // Blob URL for all local files so PDFs/docs stay previewable after send.
1202
- previewUrl: URL.createObjectURL(file),
1203
- kind
1204
- };
1205
- });
1206
- }
1207
1159
  function useChatV2(config, callbacks = {}) {
1208
1160
  const [messages, setMessages] = useState(() => getStoredOrInitialMessages(config));
1209
1161
  const [isWaitingForResponse, setIsWaitingForResponse] = useState(() => {
1210
1162
  if (!config.userId) return false;
1211
1163
  return activeStreamStore.get(config.userId)?.isWaiting ?? false;
1212
1164
  });
1213
- const [isUploadingAttachments, setIsUploadingAttachments] = useState(false);
1214
1165
  const sessionIdRef = useRef(
1215
1166
  getSessionIdFromMessages(getStoredOrInitialMessages(config)) ?? config.initialSessionId ?? void 0
1216
1167
  );
@@ -1253,7 +1204,28 @@ function useChatV2(config, callbacks = {}) {
1253
1204
  // eslint-disable-next-line react-hooks/exhaustive-deps
1254
1205
  []
1255
1206
  );
1256
- const [userActionState, setUserActionState] = useState(EMPTY_USER_ACTION_STATE);
1207
+ const [userActionState, setUserActionState] = useState(() => {
1208
+ if (!config.userId) return EMPTY_USER_ACTION_STATE2;
1209
+ return activeStreamStore.get(config.userId)?.userActionState ?? EMPTY_USER_ACTION_STATE2;
1210
+ });
1211
+ const storeAwareSetUserActionState = useCallback(
1212
+ (updater) => {
1213
+ const streamUserId = streamUserIdRef.current;
1214
+ const currentUserId = configRef.current.userId;
1215
+ const storeKey = streamUserId ?? currentUserId;
1216
+ if (storeKey && activeStreamStore.has(storeKey)) {
1217
+ activeStreamStore.applyUserActionState(
1218
+ storeKey,
1219
+ updater
1220
+ );
1221
+ }
1222
+ if (!streamUserId || streamUserId === currentUserId) {
1223
+ setUserActionState(updater);
1224
+ }
1225
+ },
1226
+ // eslint-disable-next-line react-hooks/exhaustive-deps
1227
+ []
1228
+ );
1257
1229
  const wrappedCallbacks = useMemo(() => ({
1258
1230
  ...callbacksRef.current,
1259
1231
  onMessageSent: (message) => callbacksRef.current.onMessageSent?.(message),
@@ -1263,14 +1235,14 @@ function useChatV2(config, callbacks = {}) {
1263
1235
  onExecutionTraceClick: (data) => callbacksRef.current.onExecutionTraceClick?.(data),
1264
1236
  onSessionIdChange: (sessionId) => callbacksRef.current.onSessionIdChange?.(sessionId),
1265
1237
  onUserActionRequired: (request) => {
1266
- setUserActionState((prev) => ({
1238
+ storeAwareSetUserActionState((prev) => ({
1267
1239
  ...prev,
1268
1240
  prompts: upsertPrompt(prev.prompts, request)
1269
1241
  }));
1270
1242
  callbacksRef.current.onUserActionRequired?.(request);
1271
1243
  },
1272
1244
  onUserNotification: (notification) => {
1273
- setUserActionState(
1245
+ storeAwareSetUserActionState(
1274
1246
  (prev) => prev.notifications.some((n) => n.id === notification.id) ? prev : { ...prev, notifications: [...prev.notifications, notification] }
1275
1247
  );
1276
1248
  callbacksRef.current.onUserNotification?.(notification);
@@ -1285,45 +1257,21 @@ function useChatV2(config, callbacks = {}) {
1285
1257
  );
1286
1258
  const sendMessage = useCallback(
1287
1259
  async (userMessage, options) => {
1288
- const trimmedMessage = userMessage.trim();
1289
- const files = options?.files ?? [];
1290
- const hasPreuploadedAttachments = (options?.attachments?.length ?? 0) > 0;
1291
- if (!trimmedMessage && files.length === 0 && !hasPreuploadedAttachments) return;
1292
- let streamAttachments = options?.attachments;
1293
- if (!streamAttachments && files.length > 0) {
1294
- setIsUploadingAttachments(true);
1295
- try {
1296
- streamAttachments = await uploadAttachments(configRef.current, files);
1297
- } catch (error) {
1298
- if (error.name !== "AbortError") {
1299
- callbacksRef.current.onError?.(error);
1300
- }
1301
- throw error;
1302
- } finally {
1303
- setIsUploadingAttachments(false);
1304
- }
1305
- }
1260
+ if (!userMessage.trim()) return;
1306
1261
  if (!sessionIdRef.current && configRef.current.autoGenerateSessionId !== false) {
1307
1262
  sessionIdRef.current = generateId();
1308
1263
  callbacksRef.current.onSessionIdChange?.(sessionIdRef.current);
1309
1264
  }
1310
- const messageAttachments = files.length > 0 ? buildMessageAttachments(files) : streamAttachments?.length ? streamAttachments.map((attachment, index) => ({
1311
- id: `att-${Date.now()}-${index}`,
1312
- filename: attachment.filename,
1313
- mimeType: attachment.mimeType,
1314
- kind: attachment.mimeType.startsWith("image/") ? "image" : "file"
1315
- })) : void 0;
1316
1265
  const userMessageId = `user-${Date.now()}`;
1317
1266
  const userMsg = {
1318
1267
  id: userMessageId,
1319
1268
  sessionId: sessionIdRef.current,
1320
1269
  role: "user",
1321
- content: trimmedMessage,
1322
- timestamp: (/* @__PURE__ */ new Date()).toISOString(),
1323
- attachments: messageAttachments
1270
+ content: userMessage,
1271
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
1324
1272
  };
1325
1273
  setMessages((prev) => [...prev, userMsg]);
1326
- callbacksRef.current.onMessageSent?.(trimmedMessage);
1274
+ callbacksRef.current.onMessageSent?.(userMessage);
1327
1275
  setIsWaitingForResponse(true);
1328
1276
  callbacksRef.current.onStreamStart?.();
1329
1277
  const streamingId = `assistant-${Date.now()}`;
@@ -1350,14 +1298,11 @@ function useChatV2(config, callbacks = {}) {
1350
1298
  activeStreamStore.start(userId, abortController, initialMessages);
1351
1299
  }
1352
1300
  const newSessionId = await startStream(
1353
- trimmedMessage,
1301
+ userMessage,
1354
1302
  streamingId,
1355
1303
  sessionIdRef.current,
1356
1304
  abortController,
1357
- {
1358
- analysisMode: options?.analysisMode,
1359
- attachments: streamAttachments
1360
- }
1305
+ options
1361
1306
  );
1362
1307
  const finalStreamUserId = streamUserIdRef.current ?? userId;
1363
1308
  if (finalStreamUserId) {
@@ -1387,7 +1332,7 @@ function useChatV2(config, callbacks = {}) {
1387
1332
  streamUserIdRef.current = void 0;
1388
1333
  cancelStreamManager();
1389
1334
  setIsWaitingForResponse(false);
1390
- setUserActionState((prev) => ({ ...prev, prompts: [] }));
1335
+ storeAwareSetUserActionState((prev) => ({ ...prev, prompts: [] }));
1391
1336
  setMessages(
1392
1337
  (prev) => prev.map((msg) => {
1393
1338
  if (msg.isStreaming) {
@@ -1402,7 +1347,7 @@ function useChatV2(config, callbacks = {}) {
1402
1347
  return msg;
1403
1348
  })
1404
1349
  );
1405
- }, [cancelStreamManager]);
1350
+ }, [cancelStreamManager, storeAwareSetUserActionState]);
1406
1351
  const resetSession = useCallback(() => {
1407
1352
  const streamUserId = streamUserIdRef.current ?? configRef.current.userId;
1408
1353
  if (streamUserId) {
@@ -1416,7 +1361,7 @@ function useChatV2(config, callbacks = {}) {
1416
1361
  sessionIdRef.current = void 0;
1417
1362
  abortControllerRef.current?.abort();
1418
1363
  setIsWaitingForResponse(false);
1419
- setUserActionState(EMPTY_USER_ACTION_STATE);
1364
+ storeAwareSetUserActionState(EMPTY_USER_ACTION_STATE2);
1420
1365
  }, []);
1421
1366
  const getSessionId = useCallback(() => {
1422
1367
  return sessionIdRef.current;
@@ -1426,21 +1371,21 @@ function useChatV2(config, callbacks = {}) {
1426
1371
  }, [messages]);
1427
1372
  const setPromptStatus = useCallback(
1428
1373
  (userActionId, status) => {
1429
- setUserActionState((prev) => ({
1374
+ storeAwareSetUserActionState((prev) => ({
1430
1375
  ...prev,
1431
1376
  prompts: prev.prompts.map(
1432
1377
  (p) => p.userActionId === userActionId ? { ...p, status } : p
1433
1378
  )
1434
1379
  }));
1435
1380
  },
1436
- []
1381
+ [storeAwareSetUserActionState]
1437
1382
  );
1438
1383
  const removePrompt = useCallback((userActionId) => {
1439
- setUserActionState((prev) => ({
1384
+ storeAwareSetUserActionState((prev) => ({
1440
1385
  ...prev,
1441
1386
  prompts: prev.prompts.filter((p) => p.userActionId !== userActionId)
1442
1387
  }));
1443
- }, []);
1388
+ }, [storeAwareSetUserActionState]);
1444
1389
  const submitUserAction2 = useCallback(
1445
1390
  async (userActionId, content) => {
1446
1391
  setPromptStatus(userActionId, "submitting");
@@ -1495,12 +1440,25 @@ function useChatV2(config, callbacks = {}) {
1495
1440
  },
1496
1441
  [setPromptStatus]
1497
1442
  );
1443
+ const expireUserAction2 = useCallback(
1444
+ async (userActionId) => {
1445
+ setPromptStatus(userActionId, "expired");
1446
+ try {
1447
+ await expireUserAction(configRef.current, userActionId);
1448
+ } catch (error) {
1449
+ if (error instanceof UserActionStaleError) return;
1450
+ callbacksRef.current.onError?.(error);
1451
+ throw error;
1452
+ }
1453
+ },
1454
+ [setPromptStatus]
1455
+ );
1498
1456
  const dismissNotification = useCallback((id) => {
1499
- setUserActionState((prev) => ({
1457
+ storeAwareSetUserActionState((prev) => ({
1500
1458
  ...prev,
1501
1459
  notifications: prev.notifications.filter((n) => n.id !== id)
1502
1460
  }));
1503
- }, []);
1461
+ }, [storeAwareSetUserActionState]);
1504
1462
  useEffect(() => {
1505
1463
  const prevSubscriptionUserId = subscriptionPrevUserIdRef.current;
1506
1464
  subscriptionPrevUserIdRef.current = config.userId;
@@ -1509,14 +1467,17 @@ function useChatV2(config, callbacks = {}) {
1509
1467
  if (prevSubscriptionUserId && prevSubscriptionUserId !== userId && streamUserIdRef.current === prevSubscriptionUserId && !activeStreamStore.has(prevSubscriptionUserId) && activeStreamStore.has(userId)) {
1510
1468
  streamUserIdRef.current = userId;
1511
1469
  }
1512
- const unsubscribe = activeStreamStore.subscribe(userId, (msgs, isWaiting) => {
1470
+ const unsubscribe = activeStreamStore.subscribe(userId, (msgs, isWaiting, actions) => {
1513
1471
  setMessages(msgs);
1514
1472
  setIsWaitingForResponse(isWaiting);
1473
+ setUserActionState(actions);
1515
1474
  });
1516
1475
  const active = activeStreamStore.get(userId);
1517
1476
  if (active) {
1477
+ streamUserIdRef.current = userId;
1518
1478
  setMessages(active.messages);
1519
1479
  setIsWaitingForResponse(active.isWaiting);
1480
+ setUserActionState(active.userActionState);
1520
1481
  }
1521
1482
  return unsubscribe;
1522
1483
  }, [config.userId]);
@@ -1535,18 +1496,6 @@ function useChatV2(config, callbacks = {}) {
1535
1496
  setMessages(config.initialMessages);
1536
1497
  sessionIdRef.current = getSessionIdFromMessages(config.initialMessages) ?? config.initialSessionId;
1537
1498
  }, [config.initialMessages, config.initialSessionId, config.userId]);
1538
- const hydratedSessionIdRef = useRef(void 0);
1539
- useEffect(() => {
1540
- if (config.userId) return;
1541
- if (!config.initialMessages?.length) return;
1542
- const sessionId = config.initialSessionId;
1543
- if (hydratedSessionIdRef.current === sessionId && messagesRef.current.length > 0) {
1544
- return;
1545
- }
1546
- hydratedSessionIdRef.current = sessionId;
1547
- setMessages(config.initialMessages);
1548
- sessionIdRef.current = getSessionIdFromMessages(config.initialMessages) ?? sessionId;
1549
- }, [config.initialMessages, config.initialSessionId, config.userId]);
1550
1499
  useEffect(() => {
1551
1500
  const prevUserId = prevUserIdRef.current;
1552
1501
  prevUserIdRef.current = config.userId;
@@ -1556,12 +1505,14 @@ function useChatV2(config, callbacks = {}) {
1556
1505
  setMessages([]);
1557
1506
  sessionIdRef.current = void 0;
1558
1507
  setIsWaitingForResponse(false);
1559
- setUserActionState(EMPTY_USER_ACTION_STATE);
1508
+ setUserActionState(EMPTY_USER_ACTION_STATE2);
1560
1509
  } else if (config.userId) {
1561
1510
  const active = activeStreamStore.get(config.userId);
1562
1511
  if (active) {
1512
+ streamUserIdRef.current = config.userId;
1563
1513
  setMessages(active.messages);
1564
1514
  setIsWaitingForResponse(active.isWaiting);
1515
+ setUserActionState(active.userActionState);
1565
1516
  sessionIdRef.current = getSessionIdFromMessages(active.messages) ?? config.initialSessionId;
1566
1517
  return;
1567
1518
  }
@@ -1581,12 +1532,12 @@ function useChatV2(config, callbacks = {}) {
1581
1532
  getSessionId,
1582
1533
  getMessages,
1583
1534
  isWaitingForResponse,
1584
- isUploadingAttachments,
1585
1535
  sessionId: sessionIdRef.current,
1586
1536
  userActionState,
1587
1537
  submitUserAction: submitUserAction2,
1588
1538
  cancelUserAction: cancelUserAction2,
1589
1539
  resendUserAction: resendUserAction2,
1540
+ expireUserAction: expireUserAction2,
1590
1541
  dismissNotification
1591
1542
  };
1592
1543
  }
@@ -2120,6 +2071,9 @@ function getConflictErrorMessage(errorDetails) {
2120
2071
  }
2121
2072
  function initSentryIfNeeded(dsn) {
2122
2073
  if (!dsn) return;
2074
+ if (typeof window !== "undefined" && (window.location.hostname === "localhost" || window.location.hostname === "127.0.0.1")) {
2075
+ return;
2076
+ }
2123
2077
  if (Sentry.getClient()) return;
2124
2078
  Sentry.init({
2125
2079
  dsn,
@@ -3501,9 +3455,88 @@ function MarkdownImageV2({
3501
3455
  }
3502
3456
  ) });
3503
3457
  }
3504
- function buildComponents(onImageClick, isResolvingRef) {
3458
+ var RAG_SOURCE_CLASS = "payman-v2-rag-source";
3459
+ var RAG_FOLLOW_UP_CLASS = "payman-v2-rag-follow-up";
3460
+ var SOURCE_PATTERN = /^Sources?:\s*\S/i;
3461
+ var MAX_FOLLOW_UP_LENGTH = 320;
3462
+ var HR_PATTERN = /^-{3,}$|^_{3,}$|^\*{3,}$/;
3463
+ function normalizeRagParagraphText(text) {
3464
+ return text.replace(/\s+/g, " ").trim();
3465
+ }
3466
+ function isRagSourceParagraph(text) {
3467
+ return SOURCE_PATTERN.test(normalizeRagParagraphText(text));
3468
+ }
3469
+ function isRagFollowUpCandidate(text) {
3470
+ const normalized = normalizeRagParagraphText(text);
3471
+ return normalized.length > 0 && normalized.length <= MAX_FOLLOW_UP_LENGTH && normalized.endsWith("?");
3472
+ }
3473
+ function extractRagParagraphTexts(content) {
3474
+ const paragraphs = [];
3475
+ const currentParagraph = [];
3476
+ const flushParagraph = () => {
3477
+ const paragraph = normalizeRagParagraphText(currentParagraph.join(" "));
3478
+ currentParagraph.length = 0;
3479
+ if (paragraph) paragraphs.push(paragraph);
3480
+ };
3481
+ content.replace(/\\n/g, "\n").split(/\r?\n/).forEach((line) => {
3482
+ const trimmedLine = line.trim();
3483
+ if (!trimmedLine) {
3484
+ flushParagraph();
3485
+ return;
3486
+ }
3487
+ if (HR_PATTERN.test(trimmedLine)) {
3488
+ flushParagraph();
3489
+ return;
3490
+ }
3491
+ currentParagraph.push(trimmedLine);
3492
+ });
3493
+ flushParagraph();
3494
+ return paragraphs;
3495
+ }
3496
+ function getRagAnswerParagraphRoles(paragraphs) {
3497
+ const normalized = paragraphs.map(normalizeRagParagraphText);
3498
+ const roles = normalized.map(() => null);
3499
+ const sourceIndexes = normalized.map((paragraph, index) => isRagSourceParagraph(paragraph) ? index : -1).filter((index) => index >= 0);
3500
+ for (const sourceIndex of sourceIndexes) {
3501
+ roles[sourceIndex] = "source";
3502
+ for (const candidateIndex of [sourceIndex - 1, sourceIndex + 1]) {
3503
+ if (candidateIndex >= 0 && candidateIndex < normalized.length && roles[candidateIndex] == null && isRagFollowUpCandidate(normalized[candidateIndex] ?? "")) {
3504
+ roles[candidateIndex] = "follow-up";
3505
+ }
3506
+ }
3507
+ }
3508
+ return roles;
3509
+ }
3510
+ function getRagAnswerParagraphRole(paragraph, paragraphs) {
3511
+ const normalizedParagraph = normalizeRagParagraphText(paragraph);
3512
+ if (isRagSourceParagraph(normalizedParagraph)) return "source";
3513
+ const roles = getRagAnswerParagraphRoles(paragraphs);
3514
+ return paragraphs.map(normalizeRagParagraphText).some((candidate, index) => {
3515
+ return candidate === normalizedParagraph && roles[index] === "follow-up";
3516
+ }) ? "follow-up" : null;
3517
+ }
3518
+ function ragAnswerRoleClassName(role) {
3519
+ if (role === "source") return RAG_SOURCE_CLASS;
3520
+ if (role === "follow-up") return RAG_FOLLOW_UP_CLASS;
3521
+ return void 0;
3522
+ }
3523
+ function reactNodeText(node) {
3524
+ if (typeof node === "string" || typeof node === "number") return String(node);
3525
+ if (Array.isArray(node)) return node.map(reactNodeText).join("");
3526
+ if (React.isValidElement(node)) {
3527
+ return reactNodeText(node.props.children);
3528
+ }
3529
+ return "";
3530
+ }
3531
+ function buildComponents(onImageClick, isResolvingRef, ragParagraphsRef) {
3505
3532
  return {
3506
- p: ({ children }) => /* @__PURE__ */ jsx("p", { children }),
3533
+ p: ({ children }) => {
3534
+ const role = getRagAnswerParagraphRole(
3535
+ reactNodeText(children),
3536
+ ragParagraphsRef?.current ?? []
3537
+ );
3538
+ return /* @__PURE__ */ jsx("p", { className: ragAnswerRoleClassName(role), children });
3539
+ },
3507
3540
  code: ({ children }) => /* @__PURE__ */ jsx("code", { children }),
3508
3541
  pre: ({ children }) => /* @__PURE__ */ jsx("div", { className: "payman-v2-markdown-pre", children: /* @__PURE__ */ jsx("pre", { children }) }),
3509
3542
  ul: ({ children }) => /* @__PURE__ */ jsx("ul", { children }),
@@ -3541,8 +3574,13 @@ function MarkdownRendererV2({
3541
3574
  }) {
3542
3575
  const isResolvingRef = useRef(isResolvingImages);
3543
3576
  isResolvingRef.current = isResolvingImages;
3577
+ const ragParagraphsRef = useRef([]);
3578
+ ragParagraphsRef.current = useMemo(
3579
+ () => extractRagParagraphTexts(content),
3580
+ [content]
3581
+ );
3544
3582
  const components = useMemo(
3545
- () => buildComponents(onImageClick, isResolvingRef),
3583
+ () => buildComponents(onImageClick, isResolvingRef, ragParagraphsRef),
3546
3584
  [onImageClick]
3547
3585
  );
3548
3586
  return /* @__PURE__ */ jsx(
@@ -4035,6 +4073,7 @@ function AssistantMessageV2({
4035
4073
  return message.isStreaming ? stripIncompleteImageToken(normalized) : normalized;
4036
4074
  })();
4037
4075
  const isThinkingStreaming = !!message.isStreaming && !rawResponseContent && !message.isError;
4076
+ const showTrailingProgress = !!message.isStreaming && !!rawResponseContent && !message.isError && !message.isCancelled && !!message.hasTrailingActivity;
4038
4077
  const responseTypingEnabled = hasEverStreamed.current && Boolean(rawResponseContent) && !message.isError;
4039
4078
  const { displayedText: displayContent, isTyping: isResponseTyping } = useTypingEffect(
4040
4079
  rawResponseContent,
@@ -4193,7 +4232,7 @@ function AssistantMessageV2({
4193
4232
  {
4194
4233
  isStreaming: isThinkingStreaming,
4195
4234
  stickyLabel,
4196
- currentStepLabel
4235
+ currentStepLabel: currentStepLabel ?? message.currentMessage
4197
4236
  }
4198
4237
  )
4199
4238
  },
@@ -4208,6 +4247,27 @@ function AssistantMessageV2({
4208
4247
  onImageClick
4209
4248
  }
4210
4249
  ) : !isThinkingStreaming ? /* @__PURE__ */ jsx("span", { className: "payman-v2-assistant-msg-placeholder", children: "..." }) : null }),
4250
+ /* @__PURE__ */ jsx(AnimatePresence, { children: showTrailingProgress && /* @__PURE__ */ jsx(
4251
+ motion.div,
4252
+ {
4253
+ initial: { opacity: 0, height: 0 },
4254
+ animate: { opacity: 1, height: "auto" },
4255
+ exit: { opacity: 0, height: 0 },
4256
+ transition: {
4257
+ duration: 0.32,
4258
+ ease: [0.2, 0, 0, 1]
4259
+ },
4260
+ style: { overflow: "hidden" },
4261
+ children: /* @__PURE__ */ jsx(
4262
+ ThinkingBlockV2,
4263
+ {
4264
+ isStreaming: true,
4265
+ currentStepLabel: message.currentMessage ?? currentStepLabel
4266
+ }
4267
+ )
4268
+ },
4269
+ "trailing-progress"
4270
+ ) }),
4211
4271
  isCancelled && message.isStreaming && /* @__PURE__ */ jsxs("div", { className: "payman-v2-assistant-msg-paused", children: [
4212
4272
  /* @__PURE__ */ jsx(
4213
4273
  WifiOff,
@@ -4502,7 +4562,10 @@ function VerificationInline({
4502
4562
  if (busy) return;
4503
4563
  void onCancel(prompt.userActionId);
4504
4564
  }, [busy, onCancel, prompt.userActionId]);
4505
- const description = prompt.message?.trim() || (isNumeric ? `Enter the ${codeLen}-digit code to continue` : "Enter the verification code to continue");
4565
+ const promptMessage = prompt.message?.trim();
4566
+ const description = promptMessage || (isNumeric ? `Enter the ${codeLen}-digit code to continue` : "Enter the verification code to continue");
4567
+ const messageFormat = prompt.metadata?.["payman/messageFormat"];
4568
+ const renderMarkdown = !!promptMessage && messageFormat === "markdown";
4506
4569
  if (hiddenAfterResend) return null;
4507
4570
  return /* @__PURE__ */ jsxs("div", { className: "payman-v2-ua", role: "group", "aria-label": "Verification required", children: [
4508
4571
  /* @__PURE__ */ jsxs("div", { className: "payman-v2-ua-head", children: [
@@ -4510,7 +4573,7 @@ function VerificationInline({
4510
4573
  /* @__PURE__ */ jsx("span", { className: "payman-v2-ua-title", children: "Verification required" }),
4511
4574
  typeof secondsLeft === "number" && !stale && /* @__PURE__ */ jsx("span", { className: "payman-v2-ua-timer", children: expired ? "Expired" : `${secondsLeft}s` })
4512
4575
  ] }),
4513
- /* @__PURE__ */ jsx("p", { className: "payman-v2-ua-desc", children: description }),
4576
+ renderMarkdown ? /* @__PURE__ */ jsx("div", { className: "payman-v2-ua-markdown", children: /* @__PURE__ */ jsx(MarkdownRendererV2, { content: description }) }) : /* @__PURE__ */ jsx("p", { className: "payman-v2-ua-desc", children: description }),
4514
4577
  stale ? /* @__PURE__ */ jsx("p", { className: "payman-v2-ua-stale", children: "This request is no longer available." }) : /* @__PURE__ */ jsxs(Fragment, { children: [
4515
4578
  isNumeric ? /* @__PURE__ */ jsx("div", { className: "payman-v2-ua-field", children: /* @__PURE__ */ jsx(
4516
4579
  OtpInputV2,
@@ -4734,14 +4797,15 @@ function NotificationInline({ notification, onDismiss }) {
4734
4797
  ] });
4735
4798
  }
4736
4799
  function useExpiryCountdown(prompt) {
4737
- const initial = typeof prompt.expirySeconds === "number" && prompt.expirySeconds > 0 ? Math.floor(prompt.expirySeconds) : void 0;
4738
- const [secondsLeft, setSecondsLeft] = useState(initial);
4800
+ const deadlineMs = typeof prompt.expiresAt === "number" && prompt.expiresAt > 0 ? prompt.expiresAt : typeof prompt.expirySeconds === "number" && prompt.expirySeconds > 0 ? Date.now() + Math.floor(prompt.expirySeconds) * 1e3 : void 0;
4801
+ const [secondsLeft, setSecondsLeft] = useState(
4802
+ () => deadlineMs !== void 0 ? Math.max(0, Math.ceil((deadlineMs - Date.now()) / 1e3)) : void 0
4803
+ );
4739
4804
  useEffect(() => {
4740
- if (initial === void 0) {
4805
+ if (deadlineMs === void 0) {
4741
4806
  setSecondsLeft(void 0);
4742
4807
  return;
4743
4808
  }
4744
- const deadlineMs = Date.now() + initial * 1e3;
4745
4809
  let intervalId;
4746
4810
  const remaining = () => Math.max(0, Math.ceil((deadlineMs - Date.now()) / 1e3));
4747
4811
  const sync = () => {
@@ -4763,8 +4827,8 @@ function useExpiryCountdown(prompt) {
4763
4827
  window.removeEventListener("focus", sync);
4764
4828
  window.removeEventListener("pageshow", sync);
4765
4829
  };
4766
- }, [prompt.userActionId, prompt.subAction, initial]);
4767
- const expired = initial !== void 0 && secondsLeft === 0;
4830
+ }, [prompt.userActionId, prompt.subAction, deadlineMs]);
4831
+ const expired = deadlineMs !== void 0 && (getUserActionSecondsLeft(prompt) ?? secondsLeft ?? 0) <= 0;
4768
4832
  return [secondsLeft, expired];
4769
4833
  }
4770
4834
  function UserActionInline({
@@ -6319,7 +6383,7 @@ var PaymanChatInner = forwardRef(function PaymanChatInner2({
6319
6383
  const submitUserAction2 = chat.submitUserAction ?? NOOP_ASYNC;
6320
6384
  const cancelUserAction2 = chat.cancelUserAction ?? NOOP_ASYNC;
6321
6385
  const resendUserAction2 = chat.resendUserAction ?? NOOP_ASYNC;
6322
- const expireUserAction = chat.expireUserAction ?? NOOP_ASYNC;
6386
+ const expireUserAction2 = chat.expireUserAction ?? NOOP_ASYNC;
6323
6387
  const dismissNotification = chat.dismissNotification ?? NOOP;
6324
6388
  const isUserActionSupported = typeof chat.submitUserAction === "function" && typeof chat.cancelUserAction === "function" && typeof chat.resendUserAction === "function";
6325
6389
  const {
@@ -6720,7 +6784,7 @@ var PaymanChatInner = forwardRef(function PaymanChatInner2({
6720
6784
  onSubmitUserAction: isUserActionSupported ? submitUserAction2 : void 0,
6721
6785
  onCancelUserAction: isUserActionSupported ? cancelUserAction2 : void 0,
6722
6786
  onResendUserAction: isUserActionSupported ? resendUserAction2 : void 0,
6723
- onExpireUserAction: isUserActionSupported ? expireUserAction : void 0,
6787
+ onExpireUserAction: isUserActionSupported ? expireUserAction2 : void 0,
6724
6788
  onDismissNotification: dismissNotification,
6725
6789
  onSubmitFeedback: handleSubmitFeedback
6726
6790
  }