@paymanai/payman-ask-sdk 4.0.22 → 4.0.24

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.
@@ -517,9 +517,9 @@ function processStreamEventV2(rawEvent, state) {
517
517
  state.hasError = true;
518
518
  state.errorMessage = "WORKFLOW_FAILED";
519
519
  }
520
- state.steps.forEach((step) => {
521
- if (step.status === "in_progress") {
522
- step.status = "completed";
520
+ state.steps.forEach((step2) => {
521
+ if (step2.status === "in_progress") {
522
+ step2.status = "completed";
523
523
  }
524
524
  });
525
525
  state.lastEventType = eventType;
@@ -731,7 +731,8 @@ function buildRequestBody(config, userMessage, sessionId, options) {
731
731
  sessionAttributes,
732
732
  analysisMode: options?.analysisMode,
733
733
  locale: resolveLocale(config.locale),
734
- timezone: resolveTimezone(config.timezone)
734
+ timezone: resolveTimezone(config.timezone),
735
+ ...options?.attachments?.length ? { attachments: options.attachments } : {}
735
736
  };
736
737
  }
737
738
  function resolveLocale(configured) {
@@ -776,6 +777,7 @@ function buildResolveImagesUrl(config) {
776
777
  const basePath = normalizedEndpointPath.endsWith("/stream") ? normalizedEndpointPath.slice(0, -"/stream".length) : normalizedEndpointPath;
777
778
  return `${config.api.baseUrl}${basePath}/resolve-image-urls`;
778
779
  }
780
+ var NGROK_SKIP_BROWSER_WARNING = "ngrok-skip-browser-warning";
779
781
  function buildRequestHeaders(config) {
780
782
  const headers = {
781
783
  ...config.api.headers
@@ -783,6 +785,9 @@ function buildRequestHeaders(config) {
783
785
  if (config.api.authToken) {
784
786
  headers.Authorization = `Bearer ${config.api.authToken}`;
785
787
  }
788
+ if (!headers[NGROK_SKIP_BROWSER_WARNING]) {
789
+ headers[NGROK_SKIP_BROWSER_WARNING] = "true";
790
+ }
786
791
  return headers;
787
792
  }
788
793
  var RAG_IMAGE_REGEX = /\/api\/rag\/chunks\/[^"'\s]+\/image/;
@@ -934,11 +939,11 @@ function useStreamManagerV2(config, callbacks, setMessages, setIsWaitingForRespo
934
939
  errorDetails: isAborted ? void 0 : error.message,
935
940
  content: isAborted ? state.finalResponse || "" : state.finalResponse || FRIENDLY_ERROR_MESSAGE,
936
941
  currentMessage: isAborted ? "Thinking..." : void 0,
937
- steps: [...state.steps].map((step) => {
938
- if (step.status === "in_progress" && isAborted) {
939
- return { ...step, status: "pending" };
942
+ steps: [...state.steps].map((step2) => {
943
+ if (step2.status === "in_progress" && isAborted) {
944
+ return { ...step2, status: "pending" };
940
945
  }
941
- return step;
946
+ return step2;
942
947
  }),
943
948
  currentExecutingStepId: void 0
944
949
  } : msg
@@ -1024,11 +1029,11 @@ function useStreamManagerV2(config, callbacks, setMessages, setIsWaitingForRespo
1024
1029
  isCancelled: isAborted,
1025
1030
  errorDetails: isAborted ? void 0 : error.message,
1026
1031
  content: isAborted ? state.finalResponse || "" : state.finalResponse || FRIENDLY_ERROR_MESSAGE,
1027
- steps: [...state.steps].map((step) => {
1028
- if (step.status === "in_progress" && isAborted) {
1029
- return { ...step, status: "pending" };
1032
+ steps: [...state.steps].map((step2) => {
1033
+ if (step2.status === "in_progress" && isAborted) {
1034
+ return { ...step2, status: "pending" };
1030
1035
  }
1031
- return step;
1036
+ return step2;
1032
1037
  }),
1033
1038
  currentExecutingStepId: void 0
1034
1039
  } : msg
@@ -1049,11 +1054,11 @@ function useStreamManagerV2(config, callbacks, setMessages, setIsWaitingForRespo
1049
1054
  };
1050
1055
  }
1051
1056
  function createCancelledMessageUpdate(steps, currentMessage) {
1052
- const updatedSteps = steps.map((step) => {
1053
- if (step.status === "in_progress") {
1054
- return { ...step, status: "pending" };
1057
+ const updatedSteps = steps.map((step2) => {
1058
+ if (step2.status === "in_progress") {
1059
+ return { ...step2, status: "pending" };
1055
1060
  }
1056
- return step;
1061
+ return step2;
1057
1062
  });
1058
1063
  return {
1059
1064
  isStreaming: false,
@@ -1063,6 +1068,81 @@ function createCancelledMessageUpdate(steps, currentMessage) {
1063
1068
  currentMessage: currentMessage || "Thinking..."
1064
1069
  };
1065
1070
  }
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
+ }
1066
1146
  var UserActionStaleError = class extends Error {
1067
1147
  constructor(userActionId, message = "User action is no longer actionable") {
1068
1148
  super(message);
@@ -1099,9 +1179,6 @@ async function cancelUserAction(config, userActionId) {
1099
1179
  async function resendUserAction(config, userActionId) {
1100
1180
  return sendUserActionRequest(config, userActionId, "resend");
1101
1181
  }
1102
- async function expireUserAction(config, userActionId) {
1103
- return sendUserActionRequest(config, userActionId, "expired");
1104
- }
1105
1182
  var EMPTY_USER_ACTION_STATE = { prompts: [], notifications: [] };
1106
1183
  function upsertPrompt(prompts, req) {
1107
1184
  const active = { ...req, status: "pending" };
@@ -1130,12 +1207,32 @@ function getStoredOrInitialMessages(config) {
1130
1207
  function getSessionIdFromMessages(messages) {
1131
1208
  return messages.find((message) => message.sessionId)?.sessionId;
1132
1209
  }
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
+ }
1133
1229
  function useChatV2(config, callbacks = {}) {
1134
1230
  const [messages, setMessages] = react.useState(() => getStoredOrInitialMessages(config));
1135
1231
  const [isWaitingForResponse, setIsWaitingForResponse] = react.useState(() => {
1136
1232
  if (!config.userId) return false;
1137
1233
  return activeStreamStore.get(config.userId)?.isWaiting ?? false;
1138
1234
  });
1235
+ const [isUploadingAttachments, setIsUploadingAttachments] = react.useState(false);
1139
1236
  const sessionIdRef = react.useRef(
1140
1237
  getSessionIdFromMessages(getStoredOrInitialMessages(config)) ?? config.initialSessionId ?? void 0
1141
1238
  );
@@ -1210,21 +1307,45 @@ function useChatV2(config, callbacks = {}) {
1210
1307
  );
1211
1308
  const sendMessage = react.useCallback(
1212
1309
  async (userMessage, options) => {
1213
- if (!userMessage.trim()) return;
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
+ }
1214
1328
  if (!sessionIdRef.current && configRef.current.autoGenerateSessionId !== false) {
1215
1329
  sessionIdRef.current = generateId();
1216
1330
  callbacksRef.current.onSessionIdChange?.(sessionIdRef.current);
1217
1331
  }
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;
1218
1338
  const userMessageId = `user-${Date.now()}`;
1219
1339
  const userMsg = {
1220
1340
  id: userMessageId,
1221
1341
  sessionId: sessionIdRef.current,
1222
1342
  role: "user",
1223
- content: userMessage,
1224
- timestamp: (/* @__PURE__ */ new Date()).toISOString()
1343
+ content: trimmedMessage,
1344
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
1345
+ attachments: messageAttachments
1225
1346
  };
1226
1347
  setMessages((prev) => [...prev, userMsg]);
1227
- callbacksRef.current.onMessageSent?.(userMessage);
1348
+ callbacksRef.current.onMessageSent?.(trimmedMessage);
1228
1349
  setIsWaitingForResponse(true);
1229
1350
  callbacksRef.current.onStreamStart?.();
1230
1351
  const streamingId = `assistant-${Date.now()}`;
@@ -1251,11 +1372,14 @@ function useChatV2(config, callbacks = {}) {
1251
1372
  activeStreamStore.start(userId, abortController, initialMessages);
1252
1373
  }
1253
1374
  const newSessionId = await startStream(
1254
- userMessage,
1375
+ trimmedMessage,
1255
1376
  streamingId,
1256
1377
  sessionIdRef.current,
1257
1378
  abortController,
1258
- options
1379
+ {
1380
+ analysisMode: options?.analysisMode,
1381
+ attachments: streamAttachments
1382
+ }
1259
1383
  );
1260
1384
  const finalStreamUserId = streamUserIdRef.current ?? userId;
1261
1385
  if (finalStreamUserId) {
@@ -1393,19 +1517,6 @@ function useChatV2(config, callbacks = {}) {
1393
1517
  },
1394
1518
  [setPromptStatus]
1395
1519
  );
1396
- const expireUserAction2 = react.useCallback(
1397
- async (userActionId) => {
1398
- setPromptStatus(userActionId, "expired");
1399
- try {
1400
- await expireUserAction(configRef.current, userActionId);
1401
- } catch (error) {
1402
- if (error instanceof UserActionStaleError) return;
1403
- callbacksRef.current.onError?.(error);
1404
- throw error;
1405
- }
1406
- },
1407
- [setPromptStatus]
1408
- );
1409
1520
  const dismissNotification = react.useCallback((id) => {
1410
1521
  setUserActionState((prev) => ({
1411
1522
  ...prev,
@@ -1446,6 +1557,18 @@ function useChatV2(config, callbacks = {}) {
1446
1557
  setMessages(config.initialMessages);
1447
1558
  sessionIdRef.current = getSessionIdFromMessages(config.initialMessages) ?? config.initialSessionId;
1448
1559
  }, [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]);
1449
1572
  react.useEffect(() => {
1450
1573
  const prevUserId = prevUserIdRef.current;
1451
1574
  prevUserIdRef.current = config.userId;
@@ -1480,12 +1603,12 @@ function useChatV2(config, callbacks = {}) {
1480
1603
  getSessionId,
1481
1604
  getMessages,
1482
1605
  isWaitingForResponse,
1606
+ isUploadingAttachments,
1483
1607
  sessionId: sessionIdRef.current,
1484
1608
  userActionState,
1485
1609
  submitUserAction: submitUserAction2,
1486
1610
  cancelUserAction: cancelUserAction2,
1487
1611
  resendUserAction: resendUserAction2,
1488
- expireUserAction: expireUserAction2,
1489
1612
  dismissNotification
1490
1613
  };
1491
1614
  }
@@ -1828,6 +1951,85 @@ function usePaymanChat() {
1828
1951
  }
1829
1952
  return context;
1830
1953
  }
1954
+
1955
+ // src/utils/markdownImageToken.ts
1956
+ function step(text, i) {
1957
+ if (text[i] === "\\" && i + 1 < text.length) return i + 2;
1958
+ return i + 1;
1959
+ }
1960
+ function scanAltText(text, start) {
1961
+ let i = start;
1962
+ while (i < text.length) {
1963
+ if (text[i] === "]") return i;
1964
+ i = step(text, i);
1965
+ }
1966
+ return -1;
1967
+ }
1968
+ function scanOptionalTitle(text, start) {
1969
+ let i = start;
1970
+ while (i < text.length && /[\t\n ]/.test(text[i])) i++;
1971
+ if (i >= text.length) return i;
1972
+ const quote = text[i];
1973
+ if (quote !== '"' && quote !== "'") return i;
1974
+ i++;
1975
+ while (i < text.length) {
1976
+ if (text[i] === quote) return i + 1;
1977
+ i = step(text, i);
1978
+ }
1979
+ return -1;
1980
+ }
1981
+ function scanAngleBracketDestination(text, start) {
1982
+ let i = start + 1;
1983
+ while (i < text.length) {
1984
+ if (text[i] === ">") return i + 1;
1985
+ i = step(text, i);
1986
+ }
1987
+ return -1;
1988
+ }
1989
+ function scanRawDestination(text, start) {
1990
+ let i = start;
1991
+ let depth = 1;
1992
+ while (i < text.length) {
1993
+ const char = text[i];
1994
+ if (char === "\\") {
1995
+ if (i + 1 >= text.length) return -1;
1996
+ i += 2;
1997
+ continue;
1998
+ }
1999
+ if (char === "(") depth++;
2000
+ else if (char === ")") {
2001
+ depth--;
2002
+ if (depth === 0) return i + 1;
2003
+ }
2004
+ i++;
2005
+ }
2006
+ return -1;
2007
+ }
2008
+ function completeMarkdownImageTokenLength(text) {
2009
+ if (!text.startsWith("![")) return 0;
2010
+ const altEnd = scanAltText(text, 2);
2011
+ if (altEnd === -1 || altEnd + 1 >= text.length || text[altEnd + 1] !== "(") {
2012
+ return 0;
2013
+ }
2014
+ let i = altEnd + 2;
2015
+ if (i >= text.length) return 0;
2016
+ if (text[i] === "<") {
2017
+ i = scanAngleBracketDestination(text, i);
2018
+ if (i === -1) return 0;
2019
+ i = scanOptionalTitle(text, i);
2020
+ if (i === -1 || i >= text.length || text[i] !== ")") return 0;
2021
+ return i + 1;
2022
+ }
2023
+ i = scanRawDestination(text, i);
2024
+ return i === -1 ? 0 : i;
2025
+ }
2026
+ function stripIncompleteImageToken(text) {
2027
+ const lastBang = text.lastIndexOf("![");
2028
+ if (lastBang === -1) return text;
2029
+ const suffix = text.slice(lastBang);
2030
+ if (completeMarkdownImageTokenLength(suffix) > 0) return text;
2031
+ return text.slice(0, lastBang);
2032
+ }
1831
2033
  if (reactNative.Platform.OS === "android" && reactNative.UIManager.setLayoutAnimationEnabledExperimental) {
1832
2034
  reactNative.UIManager.setLayoutAnimationEnabledExperimental(true);
1833
2035
  }
@@ -2280,13 +2482,6 @@ function UserBubble({ message, accent }) {
2280
2482
  if (!text) return null;
2281
2483
  return /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: [sd.bubbleRow, sd.rowUser], children: /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: [sd.bubble, sd.bubbleUser, { backgroundColor: accent }], children: /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [sd.bubbleText, sd.textUser], children: text }) }) });
2282
2484
  }
2283
- function stripIncompleteImageToken(text) {
2284
- const lastBang = text.lastIndexOf("![");
2285
- if (lastBang === -1) return text;
2286
- const after = text.slice(lastBang);
2287
- if (/^!\[[^\]]*\]\([^)]*\)/.test(after)) return text;
2288
- return text.slice(0, lastBang);
2289
- }
2290
2485
  function stripDuplicatePromptText(text, promptText) {
2291
2486
  if (!promptText) return text;
2292
2487
  const pm = promptText.replace(/\\n/g, "\n").trim();