@paymanai/payman-ask-sdk 4.0.24 → 4.0.25

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.
@@ -55,6 +55,7 @@ var chatStore = {
55
55
  memoryStore.delete(key);
56
56
  }
57
57
  };
58
+ var EMPTY_USER_ACTION_STATE = { prompts: [], notifications: [] };
58
59
  var streams = /* @__PURE__ */ new Map();
59
60
  var activeStreamStore = {
60
61
  has(key) {
@@ -63,7 +64,11 @@ var activeStreamStore = {
63
64
  get(key) {
64
65
  const entry = streams.get(key);
65
66
  if (!entry) return null;
66
- return { messages: entry.messages, isWaiting: entry.isWaiting };
67
+ return {
68
+ messages: entry.messages,
69
+ isWaiting: entry.isWaiting,
70
+ userActionState: entry.userActionState
71
+ };
67
72
  },
68
73
  // Called before startStream — registers the controller and initial messages
69
74
  start(key, abortController, initialMessages) {
@@ -71,6 +76,7 @@ var activeStreamStore = {
71
76
  streams.set(key, {
72
77
  messages: initialMessages,
73
78
  isWaiting: true,
79
+ userActionState: EMPTY_USER_ACTION_STATE,
74
80
  abortController,
75
81
  listeners: existing?.listeners ?? /* @__PURE__ */ new Set()
76
82
  });
@@ -81,20 +87,27 @@ var activeStreamStore = {
81
87
  if (!entry) return;
82
88
  const next = typeof updater === "function" ? updater(entry.messages) : updater;
83
89
  entry.messages = next;
84
- entry.listeners.forEach((l) => l(next, entry.isWaiting));
90
+ entry.listeners.forEach((l) => l(next, entry.isWaiting, entry.userActionState));
85
91
  },
86
92
  setWaiting(key, waiting) {
87
93
  const entry = streams.get(key);
88
94
  if (!entry) return;
89
95
  entry.isWaiting = waiting;
90
- entry.listeners.forEach((l) => l(entry.messages, waiting));
96
+ entry.listeners.forEach((l) => l(entry.messages, waiting, entry.userActionState));
97
+ },
98
+ applyUserActionState(key, updater) {
99
+ const entry = streams.get(key);
100
+ if (!entry) return;
101
+ const next = typeof updater === "function" ? updater(entry.userActionState) : updater;
102
+ entry.userActionState = next;
103
+ entry.listeners.forEach((l) => l(entry.messages, entry.isWaiting, next));
91
104
  },
92
105
  // Called when stream completes — persists to chatStore and cleans up
93
106
  complete(key) {
94
107
  const entry = streams.get(key);
95
108
  if (!entry) return;
96
109
  entry.isWaiting = false;
97
- entry.listeners.forEach((l) => l(entry.messages, false));
110
+ entry.listeners.forEach((l) => l(entry.messages, false, entry.userActionState));
98
111
  const toSave = entry.messages.filter((m) => !m.isStreaming);
99
112
  if (toSave.length > 0) chatStore.set(key, toSave);
100
113
  streams.delete(key);
@@ -731,8 +744,7 @@ function buildRequestBody(config, userMessage, sessionId, options) {
731
744
  sessionAttributes,
732
745
  analysisMode: options?.analysisMode,
733
746
  locale: resolveLocale(config.locale),
734
- timezone: resolveTimezone(config.timezone),
735
- ...options?.attachments?.length ? { attachments: options.attachments } : {}
747
+ timezone: resolveTimezone(config.timezone)
736
748
  };
737
749
  }
738
750
  function resolveLocale(configured) {
@@ -777,7 +789,6 @@ function buildResolveImagesUrl(config) {
777
789
  const basePath = normalizedEndpointPath.endsWith("/stream") ? normalizedEndpointPath.slice(0, -"/stream".length) : normalizedEndpointPath;
778
790
  return `${config.api.baseUrl}${basePath}/resolve-image-urls`;
779
791
  }
780
- var NGROK_SKIP_BROWSER_WARNING = "ngrok-skip-browser-warning";
781
792
  function buildRequestHeaders(config) {
782
793
  const headers = {
783
794
  ...config.api.headers
@@ -785,9 +796,6 @@ function buildRequestHeaders(config) {
785
796
  if (config.api.authToken) {
786
797
  headers.Authorization = `Bearer ${config.api.authToken}`;
787
798
  }
788
- if (!headers[NGROK_SKIP_BROWSER_WARNING]) {
789
- headers[NGROK_SKIP_BROWSER_WARNING] = "true";
790
- }
791
799
  return headers;
792
800
  }
793
801
  var RAG_IMAGE_REGEX = /\/api\/rag\/chunks\/[^"'\s]+\/image/;
@@ -1068,81 +1076,6 @@ function createCancelledMessageUpdate(steps, currentMessage) {
1068
1076
  currentMessage: currentMessage || "Thinking..."
1069
1077
  };
1070
1078
  }
1071
- var DEFAULT_SIGNED_URL_ENDPOINT = "/api/files/signed-url";
1072
- function fileExtension(filename) {
1073
- const ext = filename.split(".").pop()?.toLowerCase().replace(/^\./, "");
1074
- return ext && ext.length > 0 ? ext : "bin";
1075
- }
1076
- function resolveMimeType(file) {
1077
- if (file.type && file.type.trim().length > 0) return file.type;
1078
- const ext = fileExtension(file.name);
1079
- const byExt = {
1080
- pdf: "application/pdf",
1081
- png: "image/png",
1082
- jpg: "image/jpeg",
1083
- jpeg: "image/jpeg",
1084
- gif: "image/gif",
1085
- webp: "image/webp"
1086
- };
1087
- return byExt[ext] ?? "application/octet-stream";
1088
- }
1089
- function buildSignedUrlEndpoint(config, ext) {
1090
- const endpoint = config.api.signedUrlEndpoint || DEFAULT_SIGNED_URL_ENDPOINT;
1091
- const queryParams = new URLSearchParams({ extn: ext.replace(/^\./, "") });
1092
- return `${config.api.baseUrl}${endpoint}?${queryParams.toString()}`;
1093
- }
1094
- async function requestSignedUrl(config, ext, signal) {
1095
- const url = buildSignedUrlEndpoint(config, ext);
1096
- const headers = buildRequestHeaders(config);
1097
- const response = await fetch(url, {
1098
- method: "GET",
1099
- headers,
1100
- signal
1101
- });
1102
- if (!response.ok) {
1103
- const errorText = await response.text().catch(() => "");
1104
- throw new Error(
1105
- errorText ? `Failed to get upload URL (${response.status}): ${errorText}` : `Failed to get upload URL (${response.status})`
1106
- );
1107
- }
1108
- const data = await response.json();
1109
- if (!data.key || !data.url) {
1110
- throw new Error("Signed URL response missing key or url");
1111
- }
1112
- return { key: data.key, url: data.url };
1113
- }
1114
- async function uploadToSignedUrl(signedUrl, file, mimeType, signal) {
1115
- const response = await fetch(signedUrl, {
1116
- method: "PUT",
1117
- headers: {
1118
- "Content-Type": mimeType,
1119
- "x-ms-blob-type": "BlockBlob"
1120
- },
1121
- body: file,
1122
- signal
1123
- });
1124
- if (!response.ok) {
1125
- const errorText = await response.text().catch(() => "");
1126
- throw new Error(
1127
- errorText ? `Failed to upload file (${response.status}): ${errorText}` : `Failed to upload file (${response.status})`
1128
- );
1129
- }
1130
- }
1131
- async function uploadAttachment(config, file, signal) {
1132
- const ext = fileExtension(file.name);
1133
- const mimeType = resolveMimeType(file);
1134
- const { key, url } = await requestSignedUrl(config, ext, signal);
1135
- await uploadToSignedUrl(url, file, mimeType, signal);
1136
- return {
1137
- tempKey: key,
1138
- filename: file.name,
1139
- mimeType
1140
- };
1141
- }
1142
- async function uploadAttachments(config, files, signal) {
1143
- const uploads = files.map((file) => uploadAttachment(config, file, signal));
1144
- return Promise.all(uploads);
1145
- }
1146
1079
  var UserActionStaleError = class extends Error {
1147
1080
  constructor(userActionId, message = "User action is no longer actionable") {
1148
1081
  super(message);
@@ -1179,12 +1112,38 @@ async function cancelUserAction(config, userActionId) {
1179
1112
  async function resendUserAction(config, userActionId) {
1180
1113
  return sendUserActionRequest(config, userActionId, "resend");
1181
1114
  }
1182
- var EMPTY_USER_ACTION_STATE = { prompts: [], notifications: [] };
1115
+ async function expireUserAction(config, userActionId) {
1116
+ return sendUserActionRequest(config, userActionId, "expired");
1117
+ }
1118
+ function resolveUserActionExpiresAt(req, existing) {
1119
+ if (typeof req.expirySeconds !== "number" || req.expirySeconds <= 0) {
1120
+ return void 0;
1121
+ }
1122
+ if (existing?.expiresAt && existing.userActionId === req.userActionId) {
1123
+ return existing.expiresAt;
1124
+ }
1125
+ return Date.now() + Math.floor(req.expirySeconds) * 1e3;
1126
+ }
1127
+ function getUserActionSecondsLeft(prompt) {
1128
+ if (typeof prompt.expiresAt === "number" && prompt.expiresAt > 0) {
1129
+ return Math.max(0, Math.ceil((prompt.expiresAt - Date.now()) / 1e3));
1130
+ }
1131
+ if (typeof prompt.expirySeconds === "number" && prompt.expirySeconds > 0) {
1132
+ return Math.floor(prompt.expirySeconds);
1133
+ }
1134
+ return void 0;
1135
+ }
1136
+ function isUserActionExpired(prompt) {
1137
+ const left = getUserActionSecondsLeft(prompt);
1138
+ return left !== void 0 && left <= 0;
1139
+ }
1140
+ var EMPTY_USER_ACTION_STATE2 = { prompts: [], notifications: [] };
1183
1141
  function upsertPrompt(prompts, req) {
1184
- const active = { ...req, status: "pending" };
1185
1142
  const idx = prompts.findIndex(
1186
1143
  (p) => req.toolCallId ? p.toolCallId === req.toolCallId : p.userActionId === req.userActionId
1187
1144
  );
1145
+ const expiresAt = resolveUserActionExpiresAt(req, idx >= 0 ? prompts[idx] : void 0);
1146
+ const active = { ...req, status: "pending", expiresAt };
1188
1147
  if (idx >= 0) {
1189
1148
  const next = prompts.slice();
1190
1149
  next[idx] = active;
@@ -1207,32 +1166,12 @@ function getStoredOrInitialMessages(config) {
1207
1166
  function getSessionIdFromMessages(messages) {
1208
1167
  return messages.find((message) => message.sessionId)?.sessionId;
1209
1168
  }
1210
- var IMAGE_EXTENSIONS = /* @__PURE__ */ new Set(["jpg", "jpeg", "png", "gif", "webp", "avif"]);
1211
- function attachmentKindFromFile(file) {
1212
- const ext = file.name.split(".").pop()?.toLowerCase() ?? "";
1213
- if (IMAGE_EXTENSIONS.has(ext) || file.type.startsWith("image/")) return "image";
1214
- return "file";
1215
- }
1216
- function buildMessageAttachments(files) {
1217
- return files.map((file, index) => {
1218
- const kind = attachmentKindFromFile(file);
1219
- return {
1220
- id: `att-${Date.now()}-${index}`,
1221
- filename: file.name,
1222
- mimeType: file.type || "application/octet-stream",
1223
- // Blob URL for all local files so PDFs/docs stay previewable after send.
1224
- previewUrl: URL.createObjectURL(file),
1225
- kind
1226
- };
1227
- });
1228
- }
1229
1169
  function useChatV2(config, callbacks = {}) {
1230
1170
  const [messages, setMessages] = react.useState(() => getStoredOrInitialMessages(config));
1231
1171
  const [isWaitingForResponse, setIsWaitingForResponse] = react.useState(() => {
1232
1172
  if (!config.userId) return false;
1233
1173
  return activeStreamStore.get(config.userId)?.isWaiting ?? false;
1234
1174
  });
1235
- const [isUploadingAttachments, setIsUploadingAttachments] = react.useState(false);
1236
1175
  const sessionIdRef = react.useRef(
1237
1176
  getSessionIdFromMessages(getStoredOrInitialMessages(config)) ?? config.initialSessionId ?? void 0
1238
1177
  );
@@ -1275,7 +1214,28 @@ function useChatV2(config, callbacks = {}) {
1275
1214
  // eslint-disable-next-line react-hooks/exhaustive-deps
1276
1215
  []
1277
1216
  );
1278
- const [userActionState, setUserActionState] = react.useState(EMPTY_USER_ACTION_STATE);
1217
+ const [userActionState, setUserActionState] = react.useState(() => {
1218
+ if (!config.userId) return EMPTY_USER_ACTION_STATE2;
1219
+ return activeStreamStore.get(config.userId)?.userActionState ?? EMPTY_USER_ACTION_STATE2;
1220
+ });
1221
+ const storeAwareSetUserActionState = react.useCallback(
1222
+ (updater) => {
1223
+ const streamUserId = streamUserIdRef.current;
1224
+ const currentUserId = configRef.current.userId;
1225
+ const storeKey = streamUserId ?? currentUserId;
1226
+ if (storeKey && activeStreamStore.has(storeKey)) {
1227
+ activeStreamStore.applyUserActionState(
1228
+ storeKey,
1229
+ updater
1230
+ );
1231
+ }
1232
+ if (!streamUserId || streamUserId === currentUserId) {
1233
+ setUserActionState(updater);
1234
+ }
1235
+ },
1236
+ // eslint-disable-next-line react-hooks/exhaustive-deps
1237
+ []
1238
+ );
1279
1239
  const wrappedCallbacks = react.useMemo(() => ({
1280
1240
  ...callbacksRef.current,
1281
1241
  onMessageSent: (message) => callbacksRef.current.onMessageSent?.(message),
@@ -1285,14 +1245,14 @@ function useChatV2(config, callbacks = {}) {
1285
1245
  onExecutionTraceClick: (data) => callbacksRef.current.onExecutionTraceClick?.(data),
1286
1246
  onSessionIdChange: (sessionId) => callbacksRef.current.onSessionIdChange?.(sessionId),
1287
1247
  onUserActionRequired: (request) => {
1288
- setUserActionState((prev) => ({
1248
+ storeAwareSetUserActionState((prev) => ({
1289
1249
  ...prev,
1290
1250
  prompts: upsertPrompt(prev.prompts, request)
1291
1251
  }));
1292
1252
  callbacksRef.current.onUserActionRequired?.(request);
1293
1253
  },
1294
1254
  onUserNotification: (notification) => {
1295
- setUserActionState(
1255
+ storeAwareSetUserActionState(
1296
1256
  (prev) => prev.notifications.some((n) => n.id === notification.id) ? prev : { ...prev, notifications: [...prev.notifications, notification] }
1297
1257
  );
1298
1258
  callbacksRef.current.onUserNotification?.(notification);
@@ -1307,45 +1267,21 @@ function useChatV2(config, callbacks = {}) {
1307
1267
  );
1308
1268
  const sendMessage = react.useCallback(
1309
1269
  async (userMessage, options) => {
1310
- const trimmedMessage = userMessage.trim();
1311
- const files = options?.files ?? [];
1312
- const hasPreuploadedAttachments = (options?.attachments?.length ?? 0) > 0;
1313
- if (!trimmedMessage && files.length === 0 && !hasPreuploadedAttachments) return;
1314
- let streamAttachments = options?.attachments;
1315
- if (!streamAttachments && files.length > 0) {
1316
- setIsUploadingAttachments(true);
1317
- try {
1318
- streamAttachments = await uploadAttachments(configRef.current, files);
1319
- } catch (error) {
1320
- if (error.name !== "AbortError") {
1321
- callbacksRef.current.onError?.(error);
1322
- }
1323
- throw error;
1324
- } finally {
1325
- setIsUploadingAttachments(false);
1326
- }
1327
- }
1270
+ if (!userMessage.trim()) return;
1328
1271
  if (!sessionIdRef.current && configRef.current.autoGenerateSessionId !== false) {
1329
1272
  sessionIdRef.current = generateId();
1330
1273
  callbacksRef.current.onSessionIdChange?.(sessionIdRef.current);
1331
1274
  }
1332
- const messageAttachments = files.length > 0 ? buildMessageAttachments(files) : streamAttachments?.length ? streamAttachments.map((attachment, index) => ({
1333
- id: `att-${Date.now()}-${index}`,
1334
- filename: attachment.filename,
1335
- mimeType: attachment.mimeType,
1336
- kind: attachment.mimeType.startsWith("image/") ? "image" : "file"
1337
- })) : void 0;
1338
1275
  const userMessageId = `user-${Date.now()}`;
1339
1276
  const userMsg = {
1340
1277
  id: userMessageId,
1341
1278
  sessionId: sessionIdRef.current,
1342
1279
  role: "user",
1343
- content: trimmedMessage,
1344
- timestamp: (/* @__PURE__ */ new Date()).toISOString(),
1345
- attachments: messageAttachments
1280
+ content: userMessage,
1281
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
1346
1282
  };
1347
1283
  setMessages((prev) => [...prev, userMsg]);
1348
- callbacksRef.current.onMessageSent?.(trimmedMessage);
1284
+ callbacksRef.current.onMessageSent?.(userMessage);
1349
1285
  setIsWaitingForResponse(true);
1350
1286
  callbacksRef.current.onStreamStart?.();
1351
1287
  const streamingId = `assistant-${Date.now()}`;
@@ -1372,14 +1308,11 @@ function useChatV2(config, callbacks = {}) {
1372
1308
  activeStreamStore.start(userId, abortController, initialMessages);
1373
1309
  }
1374
1310
  const newSessionId = await startStream(
1375
- trimmedMessage,
1311
+ userMessage,
1376
1312
  streamingId,
1377
1313
  sessionIdRef.current,
1378
1314
  abortController,
1379
- {
1380
- analysisMode: options?.analysisMode,
1381
- attachments: streamAttachments
1382
- }
1315
+ options
1383
1316
  );
1384
1317
  const finalStreamUserId = streamUserIdRef.current ?? userId;
1385
1318
  if (finalStreamUserId) {
@@ -1409,7 +1342,7 @@ function useChatV2(config, callbacks = {}) {
1409
1342
  streamUserIdRef.current = void 0;
1410
1343
  cancelStreamManager();
1411
1344
  setIsWaitingForResponse(false);
1412
- setUserActionState((prev) => ({ ...prev, prompts: [] }));
1345
+ storeAwareSetUserActionState((prev) => ({ ...prev, prompts: [] }));
1413
1346
  setMessages(
1414
1347
  (prev) => prev.map((msg) => {
1415
1348
  if (msg.isStreaming) {
@@ -1424,7 +1357,7 @@ function useChatV2(config, callbacks = {}) {
1424
1357
  return msg;
1425
1358
  })
1426
1359
  );
1427
- }, [cancelStreamManager]);
1360
+ }, [cancelStreamManager, storeAwareSetUserActionState]);
1428
1361
  const resetSession = react.useCallback(() => {
1429
1362
  const streamUserId = streamUserIdRef.current ?? configRef.current.userId;
1430
1363
  if (streamUserId) {
@@ -1438,7 +1371,7 @@ function useChatV2(config, callbacks = {}) {
1438
1371
  sessionIdRef.current = void 0;
1439
1372
  abortControllerRef.current?.abort();
1440
1373
  setIsWaitingForResponse(false);
1441
- setUserActionState(EMPTY_USER_ACTION_STATE);
1374
+ storeAwareSetUserActionState(EMPTY_USER_ACTION_STATE2);
1442
1375
  }, []);
1443
1376
  const getSessionId = react.useCallback(() => {
1444
1377
  return sessionIdRef.current;
@@ -1448,21 +1381,21 @@ function useChatV2(config, callbacks = {}) {
1448
1381
  }, [messages]);
1449
1382
  const setPromptStatus = react.useCallback(
1450
1383
  (userActionId, status) => {
1451
- setUserActionState((prev) => ({
1384
+ storeAwareSetUserActionState((prev) => ({
1452
1385
  ...prev,
1453
1386
  prompts: prev.prompts.map(
1454
1387
  (p) => p.userActionId === userActionId ? { ...p, status } : p
1455
1388
  )
1456
1389
  }));
1457
1390
  },
1458
- []
1391
+ [storeAwareSetUserActionState]
1459
1392
  );
1460
1393
  const removePrompt = react.useCallback((userActionId) => {
1461
- setUserActionState((prev) => ({
1394
+ storeAwareSetUserActionState((prev) => ({
1462
1395
  ...prev,
1463
1396
  prompts: prev.prompts.filter((p) => p.userActionId !== userActionId)
1464
1397
  }));
1465
- }, []);
1398
+ }, [storeAwareSetUserActionState]);
1466
1399
  const submitUserAction2 = react.useCallback(
1467
1400
  async (userActionId, content) => {
1468
1401
  setPromptStatus(userActionId, "submitting");
@@ -1517,12 +1450,25 @@ function useChatV2(config, callbacks = {}) {
1517
1450
  },
1518
1451
  [setPromptStatus]
1519
1452
  );
1453
+ const expireUserAction2 = react.useCallback(
1454
+ async (userActionId) => {
1455
+ setPromptStatus(userActionId, "expired");
1456
+ try {
1457
+ await expireUserAction(configRef.current, userActionId);
1458
+ } catch (error) {
1459
+ if (error instanceof UserActionStaleError) return;
1460
+ callbacksRef.current.onError?.(error);
1461
+ throw error;
1462
+ }
1463
+ },
1464
+ [setPromptStatus]
1465
+ );
1520
1466
  const dismissNotification = react.useCallback((id) => {
1521
- setUserActionState((prev) => ({
1467
+ storeAwareSetUserActionState((prev) => ({
1522
1468
  ...prev,
1523
1469
  notifications: prev.notifications.filter((n) => n.id !== id)
1524
1470
  }));
1525
- }, []);
1471
+ }, [storeAwareSetUserActionState]);
1526
1472
  react.useEffect(() => {
1527
1473
  const prevSubscriptionUserId = subscriptionPrevUserIdRef.current;
1528
1474
  subscriptionPrevUserIdRef.current = config.userId;
@@ -1531,14 +1477,17 @@ function useChatV2(config, callbacks = {}) {
1531
1477
  if (prevSubscriptionUserId && prevSubscriptionUserId !== userId && streamUserIdRef.current === prevSubscriptionUserId && !activeStreamStore.has(prevSubscriptionUserId) && activeStreamStore.has(userId)) {
1532
1478
  streamUserIdRef.current = userId;
1533
1479
  }
1534
- const unsubscribe = activeStreamStore.subscribe(userId, (msgs, isWaiting) => {
1480
+ const unsubscribe = activeStreamStore.subscribe(userId, (msgs, isWaiting, actions) => {
1535
1481
  setMessages(msgs);
1536
1482
  setIsWaitingForResponse(isWaiting);
1483
+ setUserActionState(actions);
1537
1484
  });
1538
1485
  const active = activeStreamStore.get(userId);
1539
1486
  if (active) {
1487
+ streamUserIdRef.current = userId;
1540
1488
  setMessages(active.messages);
1541
1489
  setIsWaitingForResponse(active.isWaiting);
1490
+ setUserActionState(active.userActionState);
1542
1491
  }
1543
1492
  return unsubscribe;
1544
1493
  }, [config.userId]);
@@ -1557,18 +1506,6 @@ function useChatV2(config, callbacks = {}) {
1557
1506
  setMessages(config.initialMessages);
1558
1507
  sessionIdRef.current = getSessionIdFromMessages(config.initialMessages) ?? config.initialSessionId;
1559
1508
  }, [config.initialMessages, config.initialSessionId, config.userId]);
1560
- const hydratedSessionIdRef = react.useRef(void 0);
1561
- react.useEffect(() => {
1562
- if (config.userId) return;
1563
- if (!config.initialMessages?.length) return;
1564
- const sessionId = config.initialSessionId;
1565
- if (hydratedSessionIdRef.current === sessionId && messagesRef.current.length > 0) {
1566
- return;
1567
- }
1568
- hydratedSessionIdRef.current = sessionId;
1569
- setMessages(config.initialMessages);
1570
- sessionIdRef.current = getSessionIdFromMessages(config.initialMessages) ?? sessionId;
1571
- }, [config.initialMessages, config.initialSessionId, config.userId]);
1572
1509
  react.useEffect(() => {
1573
1510
  const prevUserId = prevUserIdRef.current;
1574
1511
  prevUserIdRef.current = config.userId;
@@ -1578,12 +1515,14 @@ function useChatV2(config, callbacks = {}) {
1578
1515
  setMessages([]);
1579
1516
  sessionIdRef.current = void 0;
1580
1517
  setIsWaitingForResponse(false);
1581
- setUserActionState(EMPTY_USER_ACTION_STATE);
1518
+ setUserActionState(EMPTY_USER_ACTION_STATE2);
1582
1519
  } else if (config.userId) {
1583
1520
  const active = activeStreamStore.get(config.userId);
1584
1521
  if (active) {
1522
+ streamUserIdRef.current = config.userId;
1585
1523
  setMessages(active.messages);
1586
1524
  setIsWaitingForResponse(active.isWaiting);
1525
+ setUserActionState(active.userActionState);
1587
1526
  sessionIdRef.current = getSessionIdFromMessages(active.messages) ?? config.initialSessionId;
1588
1527
  return;
1589
1528
  }
@@ -1603,12 +1542,12 @@ function useChatV2(config, callbacks = {}) {
1603
1542
  getSessionId,
1604
1543
  getMessages,
1605
1544
  isWaitingForResponse,
1606
- isUploadingAttachments,
1607
1545
  sessionId: sessionIdRef.current,
1608
1546
  userActionState,
1609
1547
  submitUserAction: submitUserAction2,
1610
1548
  cancelUserAction: cancelUserAction2,
1611
1549
  resendUserAction: resendUserAction2,
1550
+ expireUserAction: expireUserAction2,
1612
1551
  dismissNotification
1613
1552
  };
1614
1553
  }
@@ -2571,35 +2510,37 @@ function uaPalette(isDark) {
2571
2510
  grabber: "rgba(15,23,42,0.18)"
2572
2511
  };
2573
2512
  }
2574
- function promptInitialSeconds(prompt) {
2575
- return prompt && typeof prompt.expirySeconds === "number" && prompt.expirySeconds > 0 ? Math.floor(prompt.expirySeconds) : void 0;
2576
- }
2577
2513
  function promptTimerKey(prompt) {
2578
- return prompt ? `${prompt.userActionId}|${prompt.subAction ?? ""}` : "";
2514
+ return prompt ? `${prompt.userActionId}|${prompt.subAction ?? ""}|${prompt.expiresAt ?? ""}` : "";
2579
2515
  }
2580
2516
  function useExpiredFlag(prompt) {
2581
- const initial = promptInitialSeconds(prompt);
2582
2517
  const key = promptTimerKey(prompt);
2583
- const [expired, setExpired] = react.useState(false);
2518
+ const [expired, setExpired] = react.useState(() => prompt ? isUserActionExpired(prompt) : false);
2584
2519
  react.useEffect(() => {
2585
- setExpired(false);
2586
- if (initial === void 0) return;
2587
- const t = setTimeout(() => setExpired(true), initial * 1e3);
2520
+ const secondsLeft = prompt ? getUserActionSecondsLeft(prompt) : void 0;
2521
+ setExpired(secondsLeft !== void 0 && secondsLeft <= 0);
2522
+ if (secondsLeft === void 0 || secondsLeft <= 0) return;
2523
+ const t = setTimeout(() => setExpired(true), secondsLeft * 1e3);
2588
2524
  return () => clearTimeout(t);
2589
- }, [key, initial]);
2525
+ }, [key, prompt]);
2590
2526
  return expired;
2591
2527
  }
2592
- function useSecondsLeft(expirySeconds, restartKey) {
2593
- const initial = typeof expirySeconds === "number" && expirySeconds > 0 ? Math.floor(expirySeconds) : void 0;
2594
- const [left, setLeft] = react.useState(initial);
2528
+ function useSecondsLeft(prompt, restartKey) {
2529
+ const deadlineMs = prompt && typeof prompt.expiresAt === "number" && prompt.expiresAt > 0 ? prompt.expiresAt : prompt && typeof prompt.expirySeconds === "number" && prompt.expirySeconds > 0 ? Date.now() + Math.floor(prompt.expirySeconds) * 1e3 : void 0;
2530
+ const [left, setLeft] = react.useState(
2531
+ () => deadlineMs !== void 0 ? Math.max(0, Math.ceil((deadlineMs - Date.now()) / 1e3)) : void 0
2532
+ );
2595
2533
  react.useEffect(() => {
2596
- setLeft(initial);
2597
- if (initial === void 0) return;
2598
- const id = setInterval(() => {
2599
- setLeft((s2) => s2 === void 0 ? s2 : s2 <= 1 ? 0 : s2 - 1);
2600
- }, 1e3);
2534
+ if (deadlineMs === void 0) {
2535
+ setLeft(void 0);
2536
+ return;
2537
+ }
2538
+ const remaining = () => Math.max(0, Math.ceil((deadlineMs - Date.now()) / 1e3));
2539
+ const sync = () => setLeft(remaining());
2540
+ sync();
2541
+ const id = setInterval(sync, 1e3);
2601
2542
  return () => clearInterval(id);
2602
- }, [restartKey, initial]);
2543
+ }, [restartKey, deadlineMs]);
2603
2544
  return left;
2604
2545
  }
2605
2546
  function SheetTimerPill({
@@ -2607,7 +2548,7 @@ function SheetTimerPill({
2607
2548
  accent,
2608
2549
  isDark
2609
2550
  }) {
2610
- const left = useSecondsLeft(prompt.expirySeconds, promptTimerKey(prompt));
2551
+ const left = useSecondsLeft(prompt, promptTimerKey(prompt));
2611
2552
  if (left === void 0 || prompt.status === "stale") return null;
2612
2553
  const expired = left <= 0;
2613
2554
  return /* @__PURE__ */ jsxRuntime.jsx(
@@ -2629,7 +2570,7 @@ function CardTimerPill({
2629
2570
  accent,
2630
2571
  isDark
2631
2572
  }) {
2632
- const left = useSecondsLeft(prompt.expirySeconds, promptTimerKey(prompt));
2573
+ const left = useSecondsLeft(prompt, promptTimerKey(prompt));
2633
2574
  if (left === void 0 || left <= 0) return null;
2634
2575
  return /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: [rc.timer, { backgroundColor: accent + (isDark ? "26" : "14") }], children: /* @__PURE__ */ jsxRuntime.jsxs(reactNative.Text, { style: { color: accent, fontSize: 12, fontWeight: "700" }, children: [
2635
2576
  left,