@reverbia/sdk 1.0.0-next.20260110221448 → 1.0.0-next.20260111110909

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.
@@ -1014,14 +1014,128 @@ function validateToken(token) {
1014
1014
  }
1015
1015
  return { valid: true };
1016
1016
  }
1017
- function createStreamAccumulator() {
1017
+ function parseReasoningTags(content, previousPartialTag = "") {
1018
+ const OPENING_TAG = "<think>";
1019
+ const CLOSING_TAG = "</think>";
1020
+ const OPENING_TAG_LEN = OPENING_TAG.length;
1021
+ const CLOSING_TAG_LEN = CLOSING_TAG.length;
1022
+ const fullContent = previousPartialTag + content;
1023
+ let messageContent = "";
1024
+ let reasoningContent = "";
1025
+ let partialTag = "";
1026
+ let i = 0;
1027
+ let insideReasoning = false;
1028
+ if (previousPartialTag) {
1029
+ if (previousPartialTag === OPENING_TAG) {
1030
+ insideReasoning = true;
1031
+ i = OPENING_TAG_LEN;
1032
+ } else if (OPENING_TAG.startsWith(previousPartialTag)) {
1033
+ if (fullContent.startsWith(OPENING_TAG)) {
1034
+ insideReasoning = true;
1035
+ i = OPENING_TAG_LEN;
1036
+ } else if (OPENING_TAG.startsWith(fullContent.slice(0, Math.min(OPENING_TAG_LEN, fullContent.length)))) {
1037
+ return {
1038
+ messageContent: "",
1039
+ reasoningContent: "",
1040
+ partialTag: fullContent.slice(0, Math.min(OPENING_TAG_LEN, fullContent.length))
1041
+ };
1042
+ } else {
1043
+ messageContent = previousPartialTag;
1044
+ i = previousPartialTag.length;
1045
+ }
1046
+ } else if (CLOSING_TAG.startsWith(previousPartialTag)) {
1047
+ if (fullContent.startsWith(CLOSING_TAG)) {
1048
+ i = CLOSING_TAG_LEN;
1049
+ insideReasoning = false;
1050
+ } else if (CLOSING_TAG.startsWith(fullContent.slice(0, Math.min(CLOSING_TAG_LEN, fullContent.length)))) {
1051
+ return {
1052
+ messageContent: "",
1053
+ reasoningContent: "",
1054
+ partialTag: fullContent.slice(0, Math.min(CLOSING_TAG_LEN, fullContent.length))
1055
+ };
1056
+ } else {
1057
+ reasoningContent = previousPartialTag;
1058
+ i = previousPartialTag.length;
1059
+ insideReasoning = true;
1060
+ }
1061
+ } else {
1062
+ if (insideReasoning) {
1063
+ reasoningContent = previousPartialTag;
1064
+ } else {
1065
+ messageContent = previousPartialTag;
1066
+ }
1067
+ i = previousPartialTag.length;
1068
+ }
1069
+ }
1070
+ while (i < fullContent.length) {
1071
+ if (insideReasoning) {
1072
+ const closeIndex = fullContent.indexOf(CLOSING_TAG, i);
1073
+ if (closeIndex === -1) {
1074
+ const remaining = fullContent.slice(i);
1075
+ if (remaining.length < CLOSING_TAG_LEN) {
1076
+ const potentialClose = remaining;
1077
+ if (CLOSING_TAG.startsWith(potentialClose)) {
1078
+ partialTag = potentialClose;
1079
+ } else {
1080
+ reasoningContent += remaining;
1081
+ }
1082
+ } else {
1083
+ reasoningContent += remaining;
1084
+ }
1085
+ break;
1086
+ }
1087
+ const contentBeforeClose = fullContent.slice(i, closeIndex);
1088
+ if (contentBeforeClose) {
1089
+ reasoningContent += contentBeforeClose;
1090
+ }
1091
+ i = closeIndex + CLOSING_TAG_LEN;
1092
+ insideReasoning = false;
1093
+ } else {
1094
+ const openIndex = fullContent.indexOf(OPENING_TAG, i);
1095
+ if (openIndex === -1) {
1096
+ const remaining = fullContent.slice(i);
1097
+ if (remaining.length < OPENING_TAG_LEN) {
1098
+ const potentialOpen = remaining;
1099
+ if (OPENING_TAG.startsWith(potentialOpen)) {
1100
+ partialTag = potentialOpen;
1101
+ } else {
1102
+ messageContent += remaining;
1103
+ }
1104
+ } else {
1105
+ messageContent += remaining;
1106
+ }
1107
+ break;
1108
+ }
1109
+ messageContent += fullContent.slice(i, openIndex);
1110
+ i = openIndex + OPENING_TAG_LEN;
1111
+ insideReasoning = true;
1112
+ }
1113
+ }
1114
+ if (messageContent.includes(OPENING_TAG) || messageContent.includes(CLOSING_TAG)) {
1115
+ console.warn("[parseReasoningTags] Warning: Tag found in messageContent, removing");
1116
+ messageContent = messageContent.replace(new RegExp(OPENING_TAG.replace(/[<>]/g, "\\$&"), "g"), "");
1117
+ messageContent = messageContent.replace(new RegExp(CLOSING_TAG.replace(/[<>]/g, "\\$&"), "g"), "");
1118
+ }
1119
+ if (reasoningContent.includes(OPENING_TAG) || reasoningContent.includes(CLOSING_TAG)) {
1120
+ console.warn("[parseReasoningTags] Warning: Tag found in reasoningContent, removing");
1121
+ reasoningContent = reasoningContent.replace(new RegExp(OPENING_TAG.replace(/[<>]/g, "\\$&"), "g"), "");
1122
+ reasoningContent = reasoningContent.replace(new RegExp(CLOSING_TAG.replace(/[<>]/g, "\\$&"), "g"), "");
1123
+ }
1124
+ return {
1125
+ messageContent,
1126
+ reasoningContent,
1127
+ partialTag
1128
+ };
1129
+ }
1130
+ function createStreamAccumulator(initialModel) {
1018
1131
  return {
1019
1132
  content: "",
1020
1133
  thinking: "",
1021
1134
  responseId: "",
1022
- responseModel: "",
1135
+ responseModel: initialModel || "",
1023
1136
  usage: {},
1024
- toolCalls: /* @__PURE__ */ new Map()
1137
+ toolCalls: /* @__PURE__ */ new Map(),
1138
+ partialReasoningTag: ""
1025
1139
  };
1026
1140
  }
1027
1141
  function createErrorResult(message, onError) {
@@ -1191,7 +1305,7 @@ var ResponsesStrategy = class {
1191
1305
  const delta = typedChunk.delta;
1192
1306
  if (delta) {
1193
1307
  const deltaText = typeof delta === "string" ? delta : delta.OfString;
1194
- if (deltaText) {
1308
+ if (deltaText && deltaText.trim().length > 0) {
1195
1309
  accumulator.content += deltaText;
1196
1310
  result.content = deltaText;
1197
1311
  }
@@ -1318,8 +1432,21 @@ var CompletionsStrategy = class {
1318
1432
  const choice = typedChunk.choices[0];
1319
1433
  if (choice.delta) {
1320
1434
  if (choice.delta.content) {
1321
- accumulator.content += choice.delta.content;
1322
- result.content = choice.delta.content;
1435
+ const parseResult = parseReasoningTags(
1436
+ choice.delta.content,
1437
+ accumulator.partialReasoningTag || ""
1438
+ );
1439
+ accumulator.content += parseResult.messageContent;
1440
+ accumulator.thinking += parseResult.reasoningContent;
1441
+ accumulator.partialReasoningTag = parseResult.partialTag;
1442
+ const willEmitMessage = parseResult.messageContent && parseResult.messageContent.trim().length > 0;
1443
+ const willEmitReasoning = parseResult.reasoningContent && parseResult.reasoningContent.trim().length > 0;
1444
+ if (willEmitMessage) {
1445
+ result.content = parseResult.messageContent;
1446
+ }
1447
+ if (willEmitReasoning) {
1448
+ result.thinking = parseResult.reasoningContent;
1449
+ }
1323
1450
  }
1324
1451
  if (choice.delta.tool_calls) {
1325
1452
  for (const toolCallDelta of choice.delta.tool_calls) {
@@ -1348,8 +1475,19 @@ var CompletionsStrategy = class {
1348
1475
  }
1349
1476
  if (choice.message) {
1350
1477
  if (choice.message.content) {
1351
- accumulator.content = choice.message.content;
1352
- result.content = choice.message.content;
1478
+ const parseResult = parseReasoningTags(
1479
+ choice.message.content,
1480
+ accumulator.partialReasoningTag || ""
1481
+ );
1482
+ accumulator.content = parseResult.messageContent;
1483
+ accumulator.thinking += parseResult.reasoningContent;
1484
+ accumulator.partialReasoningTag = parseResult.partialTag;
1485
+ if (parseResult.messageContent && parseResult.messageContent.trim().length > 0) {
1486
+ result.content = parseResult.messageContent;
1487
+ }
1488
+ if (parseResult.reasoningContent && parseResult.reasoningContent.trim().length > 0) {
1489
+ result.thinking = parseResult.reasoningContent;
1490
+ }
1353
1491
  }
1354
1492
  if (choice.message.tool_calls) {
1355
1493
  for (let i = 0; i < choice.message.tool_calls.length; i++) {
@@ -1377,6 +1515,23 @@ var CompletionsStrategy = class {
1377
1515
  }
1378
1516
  buildFinalResponse(accumulator) {
1379
1517
  const output = [];
1518
+ let finalContent = accumulator.content;
1519
+ let finalThinking = accumulator.thinking;
1520
+ if (accumulator.partialReasoningTag) {
1521
+ const finalParse = parseReasoningTags("", accumulator.partialReasoningTag);
1522
+ finalContent += finalParse.messageContent;
1523
+ if (finalParse.reasoningContent) {
1524
+ finalThinking += finalParse.reasoningContent;
1525
+ }
1526
+ }
1527
+ if (finalThinking) {
1528
+ output.push({
1529
+ type: "reasoning",
1530
+ role: "assistant",
1531
+ content: [{ type: "output_text", text: finalThinking }],
1532
+ status: "completed"
1533
+ });
1534
+ }
1380
1535
  if (accumulator.toolCalls.size > 0) {
1381
1536
  for (const toolCall of accumulator.toolCalls.values()) {
1382
1537
  output.push({
@@ -1391,7 +1546,7 @@ var CompletionsStrategy = class {
1391
1546
  output.push({
1392
1547
  type: "message",
1393
1548
  role: "assistant",
1394
- content: [{ type: "output_text", text: accumulator.content }],
1549
+ content: [{ type: "output_text", text: finalContent }],
1395
1550
  status: "completed"
1396
1551
  });
1397
1552
  return {
@@ -1571,7 +1726,7 @@ function useChat(options) {
1571
1726
  sseError = error instanceof Error ? error : new Error(String(error));
1572
1727
  }
1573
1728
  });
1574
- const accumulator = createStreamAccumulator();
1729
+ const accumulator = createStreamAccumulator(model || void 0);
1575
1730
  try {
1576
1731
  for await (const chunk of sseResult.stream) {
1577
1732
  if (isDoneMarker(chunk)) {
@@ -1766,7 +1921,7 @@ Executing tool: ${toolInfo}
1766
1921
  sseError = error instanceof Error ? error : new Error(String(error));
1767
1922
  }
1768
1923
  });
1769
- const continuationAccumulator = createStreamAccumulator();
1924
+ const continuationAccumulator = createStreamAccumulator(model || void 0);
1770
1925
  try {
1771
1926
  for await (const chunk of continuationResult.stream) {
1772
1927
  if (isDoneMarker(chunk)) {
@@ -3013,44 +3168,80 @@ var BlobUrlManager = class {
3013
3168
  // src/react/useChatStorage.ts
3014
3169
  function replaceUrlWithMCPPlaceholder(content, url, fileId) {
3015
3170
  const escapedUrl = url.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
3016
- const placeholder = `![MCP_IMAGE:${fileId}]`;
3171
+ const placeholder = createFilePlaceholder(fileId);
3017
3172
  let result = content;
3173
+ console.log(
3174
+ `[replaceUrlWithMCPPlaceholder] Replacing URL with placeholder:`,
3175
+ url,
3176
+ "->",
3177
+ placeholder
3178
+ );
3179
+ const htmlImgPatternDouble = new RegExp(
3180
+ `<img[^>]*src="${escapedUrl}"[^>]*>`,
3181
+ "gi"
3182
+ );
3183
+ const doubleMatches = result.match(htmlImgPatternDouble);
3184
+ if (doubleMatches) {
3185
+ console.log(
3186
+ `[replaceUrlWithMCPPlaceholder] Replacing ${doubleMatches.length} HTML img tag(s) with double quotes:`,
3187
+ doubleMatches,
3188
+ "->",
3189
+ placeholder
3190
+ );
3191
+ }
3192
+ result = result.replace(htmlImgPatternDouble, placeholder);
3193
+ const htmlImgPatternSingle = new RegExp(
3194
+ `<img[^>]*src='${escapedUrl}'[^>]*>`,
3195
+ "gi"
3196
+ );
3197
+ const singleMatches = result.match(htmlImgPatternSingle);
3198
+ if (singleMatches) {
3199
+ console.log(
3200
+ `[replaceUrlWithMCPPlaceholder] Replacing ${singleMatches.length} HTML img tag(s) with single quotes:`,
3201
+ singleMatches,
3202
+ "->",
3203
+ placeholder
3204
+ );
3205
+ }
3206
+ result = result.replace(htmlImgPatternSingle, placeholder);
3018
3207
  const markdownImagePattern = new RegExp(
3019
3208
  `!\\[[^\\]]*\\]\\([\\s]*${escapedUrl}[\\s]*\\)`,
3020
3209
  "g"
3021
3210
  );
3211
+ const markdownMatches = result.match(markdownImagePattern);
3212
+ if (markdownMatches) {
3213
+ console.log(
3214
+ `[replaceUrlWithMCPPlaceholder] Replacing ${markdownMatches.length} markdown image(s):`,
3215
+ markdownMatches,
3216
+ "->",
3217
+ placeholder
3218
+ );
3219
+ }
3022
3220
  result = result.replace(markdownImagePattern, placeholder);
3023
- result = result.replace(new RegExp(escapedUrl, "g"), placeholder);
3024
- const orphanedMarkdownPattern = new RegExp(
3025
- `!\\[[^\\]]*\\]\\([\\s]*\\!\\[MCP_IMAGE:${fileId}\\][\\s]*\\)`,
3026
- "g"
3221
+ const rawUrlPattern = new RegExp(escapedUrl, "g");
3222
+ const rawMatches = result.match(rawUrlPattern);
3223
+ if (rawMatches) {
3224
+ console.log(
3225
+ `[replaceUrlWithMCPPlaceholder] Replacing ${rawMatches.length} raw URL(s):`,
3226
+ rawMatches,
3227
+ "->",
3228
+ placeholder
3229
+ );
3230
+ }
3231
+ result = result.replace(rawUrlPattern, placeholder);
3232
+ console.log(
3233
+ `[replaceUrlWithMCPPlaceholder] Final result length: ${result.length}, original length: ${content.length}`
3027
3234
  );
3028
- result = result.replace(orphanedMarkdownPattern, placeholder);
3029
3235
  return result;
3030
3236
  }
3031
3237
  function findFileIdBySourceUrl(files, sourceUrl) {
3032
3238
  return files?.find((f) => f.sourceUrl === sourceUrl)?.id;
3033
3239
  }
3034
- async function isUrlValid(url, timeoutMs = 5e3) {
3035
- try {
3036
- const controller = new AbortController();
3037
- const timeoutId = setTimeout(() => controller.abort(), timeoutMs);
3038
- const response = await fetch(url, {
3039
- method: "GET",
3040
- headers: { Range: "bytes=0-0" },
3041
- signal: controller.signal
3042
- });
3043
- clearTimeout(timeoutId);
3044
- return response.ok || response.status === 206;
3045
- } catch {
3046
- return false;
3047
- }
3048
- }
3049
- async function storedToLlmapiMessage(stored) {
3240
+ function storedToLlmapiMessage(stored) {
3050
3241
  let textContent = stored.content;
3051
3242
  const fileUrlMap = /* @__PURE__ */ new Map();
3052
3243
  const imageParts = [];
3053
- if (stored.files?.length) {
3244
+ if (stored.role !== "assistant" && stored.files?.length) {
3054
3245
  for (const file of stored.files) {
3055
3246
  if (file.url) {
3056
3247
  imageParts.push({
@@ -3058,14 +3249,17 @@ async function storedToLlmapiMessage(stored) {
3058
3249
  image_url: { url: file.url }
3059
3250
  });
3060
3251
  } else if (file.sourceUrl) {
3061
- const isValid = await isUrlValid(file.sourceUrl);
3062
- if (isValid) {
3063
- imageParts.push({
3064
- type: "image_url",
3065
- image_url: { url: file.sourceUrl }
3066
- });
3067
- fileUrlMap.set(file.id, file.sourceUrl);
3068
- }
3252
+ imageParts.push({
3253
+ type: "image_url",
3254
+ image_url: { url: file.sourceUrl }
3255
+ });
3256
+ fileUrlMap.set(file.id, file.sourceUrl);
3257
+ }
3258
+ }
3259
+ } else if (stored.role === "assistant" && stored.files?.length) {
3260
+ for (const file of stored.files) {
3261
+ if (file.sourceUrl) {
3262
+ fileUrlMap.set(file.id, file.sourceUrl);
3069
3263
  }
3070
3264
  }
3071
3265
  }
@@ -3199,26 +3393,63 @@ function useChatStorage(options) {
3199
3393
  if (fileIds.length === 0) {
3200
3394
  return msg;
3201
3395
  }
3396
+ console.log(
3397
+ `[getMessages] Found ${fileIds.length} placeholder(s) in message ${msg.uniqueId}:`,
3398
+ fileIds
3399
+ );
3202
3400
  let resolvedContent = msg.content;
3203
3401
  for (const fileId of fileIds) {
3402
+ const placeholder = createFilePlaceholder(fileId);
3403
+ console.log(
3404
+ `[getMessages] Resolving placeholder: ${placeholder} (fileId: ${fileId})`
3405
+ );
3204
3406
  let url = blobManager.getUrl(fileId);
3205
3407
  if (!url) {
3408
+ console.log(
3409
+ `[getMessages] No cached URL for ${fileId}, reading from OPFS...`
3410
+ );
3206
3411
  const result = await readEncryptedFile(fileId, encryptionKey);
3207
3412
  if (result) {
3208
3413
  url = blobManager.createUrl(fileId, result.blob);
3414
+ console.log(
3415
+ `[getMessages] Created blob URL for ${fileId}:`,
3416
+ url
3417
+ );
3418
+ } else {
3419
+ console.warn(
3420
+ `[getMessages] Failed to read file ${fileId} from OPFS`
3421
+ );
3209
3422
  }
3423
+ } else {
3424
+ console.log(
3425
+ `[getMessages] Using cached blob URL for ${fileId}:`,
3426
+ url
3427
+ );
3210
3428
  }
3211
3429
  if (url) {
3212
- const placeholder = createFilePlaceholder(fileId);
3430
+ const placeholderRegex = new RegExp(
3431
+ placeholder.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"),
3432
+ "g"
3433
+ );
3434
+ const matches = resolvedContent.match(placeholderRegex);
3435
+ const replacement = `![image](${url})`;
3436
+ console.log(
3437
+ `[getMessages] Replacing ${matches?.length || 0} instance(s) of ${placeholder} with:`,
3438
+ replacement
3439
+ );
3213
3440
  resolvedContent = resolvedContent.replace(
3214
- new RegExp(
3215
- placeholder.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"),
3216
- "g"
3217
- ),
3218
- `![image](${url})`
3441
+ placeholderRegex,
3442
+ replacement
3443
+ );
3444
+ } else {
3445
+ console.warn(
3446
+ `[getMessages] No URL available for ${fileId}, placeholder ${placeholder} will remain in content`
3219
3447
  );
3220
3448
  }
3221
3449
  }
3450
+ console.log(
3451
+ `[getMessages] Resolved content length: ${resolvedContent.length}, original length: ${msg.content.length}`
3452
+ );
3222
3453
  return { ...msg, content: resolvedContent };
3223
3454
  })
3224
3455
  );
@@ -3358,16 +3589,42 @@ function useChatStorage(options) {
3358
3589
  const { replaceUrls = true } = options2 ?? {};
3359
3590
  try {
3360
3591
  const MCP_IMAGE_URL_PATTERN = new RegExp(
3361
- `https://${MCP_R2_DOMAIN.replace(/\./g, "\\.")}[^\\s)]*`,
3592
+ `https://${MCP_R2_DOMAIN.replace(/\./g, "\\.")}[^\\s"'<>)]*`,
3362
3593
  "g"
3363
3594
  );
3364
3595
  const urlMatches = content.match(MCP_IMAGE_URL_PATTERN);
3365
3596
  if (!urlMatches || urlMatches.length === 0) {
3366
3597
  return { processedFiles: [], cleanedContent: content };
3367
3598
  }
3368
- const uniqueUrls = [...new Set(urlMatches)];
3599
+ const cleanedUrls = urlMatches.map((url) => url.replace(/["']+$/, ""));
3600
+ const uniqueUrls = [...new Set(cleanedUrls)];
3369
3601
  const processedFiles = [];
3370
3602
  let cleanedContent = content;
3603
+ const urlOccurrenceCounts = /* @__PURE__ */ new Map();
3604
+ uniqueUrls.forEach((url) => {
3605
+ const escapedUrl = url.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
3606
+ const htmlDoublePattern = new RegExp(
3607
+ `<img[^>]*src="${escapedUrl}"[^>]*>`,
3608
+ "gi"
3609
+ );
3610
+ const htmlSinglePattern = new RegExp(
3611
+ `<img[^>]*src='${escapedUrl}'[^>]*>`,
3612
+ "gi"
3613
+ );
3614
+ const markdownPattern = new RegExp(
3615
+ `!\\[[^\\]]*\\]\\([\\s]*${escapedUrl}[\\s]*\\)`,
3616
+ "g"
3617
+ );
3618
+ const rawPattern = new RegExp(escapedUrl, "g");
3619
+ const htmlDoubleMatches = content.match(htmlDoublePattern)?.length || 0;
3620
+ const htmlSingleMatches = content.match(htmlSinglePattern)?.length || 0;
3621
+ const markdownMatches = content.match(markdownPattern)?.length || 0;
3622
+ const rawMatches = (content.match(rawPattern)?.length || 0) - htmlDoubleMatches - htmlSingleMatches - markdownMatches;
3623
+ urlOccurrenceCounts.set(
3624
+ url,
3625
+ htmlDoubleMatches + htmlSingleMatches + markdownMatches + rawMatches
3626
+ );
3627
+ });
3371
3628
  const results = await Promise.allSettled(
3372
3629
  uniqueUrls.map(async (imageUrl) => {
3373
3630
  const controller = new AbortController();
@@ -3399,27 +3656,85 @@ function useChatStorage(options) {
3399
3656
  );
3400
3657
  const replaceUrlWithPlaceholder = (imageUrl, fileId) => {
3401
3658
  const escapedUrl = imageUrl.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
3402
- const placeholder = `![MCP_IMAGE:${fileId}]`;
3403
- const markdownImagePattern = new RegExp(
3404
- `!\\[[^\\]]*\\]\\([\\s]*${escapedUrl}[\\s]*\\)`,
3405
- "g"
3406
- );
3407
- cleanedContent = cleanedContent.replace(
3408
- markdownImagePattern,
3659
+ const placeholder = createFilePlaceholder(fileId);
3660
+ let replacementCount = 0;
3661
+ console.log(
3662
+ `[extractAndStoreMCPImages] Replacing URL with placeholder:`,
3663
+ imageUrl,
3664
+ "->",
3409
3665
  placeholder
3410
3666
  );
3411
- cleanedContent = cleanedContent.replace(
3412
- new RegExp(escapedUrl, "g"),
3413
- placeholder
3667
+ const htmlImgPatternDouble = new RegExp(
3668
+ `<img[^>]*src="${escapedUrl}"[^>]*>`,
3669
+ "gi"
3414
3670
  );
3415
- const orphanedMarkdownPattern = new RegExp(
3416
- `!\\[[^\\]]*\\]\\([\\s]*\\!\\[MCP_IMAGE:${fileId}\\][\\s]*\\)`,
3671
+ const doubleMatches = cleanedContent.match(htmlImgPatternDouble);
3672
+ if (doubleMatches) {
3673
+ console.log(
3674
+ `[extractAndStoreMCPImages] Replacing ${doubleMatches.length} HTML img tag(s) with double quotes:`,
3675
+ doubleMatches,
3676
+ "->",
3677
+ placeholder
3678
+ );
3679
+ replacementCount += doubleMatches.length;
3680
+ cleanedContent = cleanedContent.replace(
3681
+ htmlImgPatternDouble,
3682
+ placeholder
3683
+ );
3684
+ }
3685
+ const htmlImgPatternSingle = new RegExp(
3686
+ `<img[^>]*src='${escapedUrl}'[^>]*>`,
3687
+ "gi"
3688
+ );
3689
+ const singleMatches = cleanedContent.match(htmlImgPatternSingle);
3690
+ if (singleMatches) {
3691
+ console.log(
3692
+ `[extractAndStoreMCPImages] Replacing ${singleMatches.length} HTML img tag(s) with single quotes:`,
3693
+ singleMatches,
3694
+ "->",
3695
+ placeholder
3696
+ );
3697
+ replacementCount += singleMatches.length;
3698
+ cleanedContent = cleanedContent.replace(
3699
+ htmlImgPatternSingle,
3700
+ placeholder
3701
+ );
3702
+ }
3703
+ const markdownImagePattern = new RegExp(
3704
+ `!\\[[^\\]]*\\]\\([\\s]*${escapedUrl}[\\s]*\\)`,
3417
3705
  "g"
3418
3706
  );
3419
- cleanedContent = cleanedContent.replace(
3420
- orphanedMarkdownPattern,
3421
- placeholder
3707
+ const markdownMatches = cleanedContent.match(markdownImagePattern);
3708
+ if (markdownMatches) {
3709
+ console.log(
3710
+ `[extractAndStoreMCPImages] Replacing ${markdownMatches.length} markdown image(s):`,
3711
+ markdownMatches,
3712
+ "->",
3713
+ placeholder
3714
+ );
3715
+ replacementCount += markdownMatches.length;
3716
+ cleanedContent = cleanedContent.replace(
3717
+ markdownImagePattern,
3718
+ placeholder
3719
+ );
3720
+ }
3721
+ const rawUrlPattern = new RegExp(escapedUrl, "g");
3722
+ const rawMatches = cleanedContent.match(rawUrlPattern);
3723
+ if (rawMatches) {
3724
+ console.log(
3725
+ `[extractAndStoreMCPImages] Replacing ${rawMatches.length} raw URL(s):`,
3726
+ rawMatches,
3727
+ "->",
3728
+ placeholder
3729
+ );
3730
+ replacementCount += rawMatches.length;
3731
+ cleanedContent = cleanedContent.replace(rawUrlPattern, placeholder);
3732
+ }
3733
+ console.log(
3734
+ `[extractAndStoreMCPImages] Total replacements made: ${replacementCount} for URL:`,
3735
+ imageUrl
3422
3736
  );
3737
+ return replacementCount;
3423
3738
  };
3424
3739
  results.forEach((result, i) => {
3425
3740
  const imageUrl = uniqueUrls[i];
@@ -3433,7 +3748,29 @@ function useChatStorage(options) {
3433
3748
  sourceUrl: imageUrl
3434
3749
  });
3435
3750
  if (replaceUrls && imageUrl) {
3436
- replaceUrlWithPlaceholder(imageUrl, fileId);
3751
+ const replacementCount = replaceUrlWithPlaceholder(
3752
+ imageUrl,
3753
+ fileId
3754
+ );
3755
+ const expectedCount = urlOccurrenceCounts.get(imageUrl) || 0;
3756
+ if (replacementCount < expectedCount) {
3757
+ console.warn(
3758
+ `[extractAndStoreMCPImages] Not all instances of URL replaced. Expected ${expectedCount}, replaced ${replacementCount}:`,
3759
+ imageUrl
3760
+ );
3761
+ }
3762
+ const escapedUrl = imageUrl.replace(
3763
+ /[.*+?^${}()|[\]\\]/g,
3764
+ "\\$&"
3765
+ );
3766
+ const remainingPattern = new RegExp(escapedUrl, "g");
3767
+ const remainingMatches = cleanedContent.match(remainingPattern);
3768
+ if (remainingMatches && remainingMatches.length > 0) {
3769
+ console.warn(
3770
+ `[extractAndStoreMCPImages] Found ${remainingMatches.length} remaining instance(s) of URL after replacement:`,
3771
+ imageUrl
3772
+ );
3773
+ }
3437
3774
  }
3438
3775
  } else {
3439
3776
  console.error(
@@ -3466,17 +3803,43 @@ function useChatStorage(options) {
3466
3803
  return { processedFiles: [], cleanedContent: content };
3467
3804
  }
3468
3805
  const MCP_IMAGE_URL_PATTERN = new RegExp(
3469
- `https://${MCP_R2_DOMAIN.replace(/\./g, "\\.")}[^\\s)]*`,
3806
+ `https://${MCP_R2_DOMAIN.replace(/\./g, "\\.")}[^\\s"'<>)]*`,
3470
3807
  "g"
3471
3808
  );
3472
3809
  const urlMatches = content.match(MCP_IMAGE_URL_PATTERN);
3473
3810
  if (!urlMatches || urlMatches.length === 0) {
3474
3811
  return { processedFiles: [], cleanedContent: content };
3475
3812
  }
3476
- const uniqueUrls = [...new Set(urlMatches)];
3813
+ const cleanedUrls = urlMatches.map((url) => url.replace(/["']+$/, ""));
3814
+ const uniqueUrls = [...new Set(cleanedUrls)];
3477
3815
  const encryptionKey = await getEncryptionKey(address);
3478
3816
  const processedFiles = [];
3479
3817
  let cleanedContent = content;
3818
+ const urlOccurrenceCounts = /* @__PURE__ */ new Map();
3819
+ uniqueUrls.forEach((url) => {
3820
+ const escapedUrl = url.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
3821
+ const htmlDoublePattern = new RegExp(
3822
+ `<img[^>]*src="${escapedUrl}"[^>]*>`,
3823
+ "gi"
3824
+ );
3825
+ const htmlSinglePattern = new RegExp(
3826
+ `<img[^>]*src='${escapedUrl}'[^>]*>`,
3827
+ "gi"
3828
+ );
3829
+ const markdownPattern = new RegExp(
3830
+ `!\\[[^\\]]*\\]\\([\\s]*${escapedUrl}[\\s]*\\)`,
3831
+ "g"
3832
+ );
3833
+ const rawPattern = new RegExp(escapedUrl, "g");
3834
+ const htmlDoubleMatches = content.match(htmlDoublePattern)?.length || 0;
3835
+ const htmlSingleMatches = content.match(htmlSinglePattern)?.length || 0;
3836
+ const markdownMatches = content.match(markdownPattern)?.length || 0;
3837
+ const rawMatches = (content.match(rawPattern)?.length || 0) - htmlDoubleMatches - htmlSingleMatches - markdownMatches;
3838
+ urlOccurrenceCounts.set(
3839
+ url,
3840
+ htmlDoubleMatches + htmlSingleMatches + markdownMatches + rawMatches
3841
+ );
3842
+ });
3480
3843
  const results = await Promise.allSettled(
3481
3844
  uniqueUrls.map(async (imageUrl) => {
3482
3845
  const controller = new AbortController();
@@ -3518,12 +3881,98 @@ function useChatStorage(options) {
3518
3881
  });
3519
3882
  const placeholder = createFilePlaceholder(fileId);
3520
3883
  const escapedUrl = imageUrl.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
3884
+ let replacementCount = 0;
3885
+ console.log(
3886
+ `[extractAndStoreEncryptedMCPImages] Replacing URL with placeholder:`,
3887
+ imageUrl,
3888
+ "->",
3889
+ placeholder
3890
+ );
3891
+ const htmlImgPatternDouble = new RegExp(
3892
+ `<img[^>]*src="${escapedUrl}"[^>]*>`,
3893
+ "gi"
3894
+ );
3895
+ const doubleMatches = cleanedContent.match(htmlImgPatternDouble);
3896
+ if (doubleMatches) {
3897
+ console.log(
3898
+ `[extractAndStoreEncryptedMCPImages] Replacing ${doubleMatches.length} HTML img tag(s) with double quotes:`,
3899
+ doubleMatches,
3900
+ "->",
3901
+ placeholder
3902
+ );
3903
+ replacementCount += doubleMatches.length;
3904
+ cleanedContent = cleanedContent.replace(
3905
+ htmlImgPatternDouble,
3906
+ placeholder
3907
+ );
3908
+ }
3909
+ const htmlImgPatternSingle = new RegExp(
3910
+ `<img[^>]*src='${escapedUrl}'[^>]*>`,
3911
+ "gi"
3912
+ );
3913
+ const singleMatches = cleanedContent.match(htmlImgPatternSingle);
3914
+ if (singleMatches) {
3915
+ console.log(
3916
+ `[extractAndStoreEncryptedMCPImages] Replacing ${singleMatches.length} HTML img tag(s) with single quotes:`,
3917
+ singleMatches,
3918
+ "->",
3919
+ placeholder
3920
+ );
3921
+ replacementCount += singleMatches.length;
3922
+ cleanedContent = cleanedContent.replace(
3923
+ htmlImgPatternSingle,
3924
+ placeholder
3925
+ );
3926
+ }
3521
3927
  const markdownImagePattern = new RegExp(
3522
3928
  `!\\[[^\\]]*\\]\\([\\s]*${escapedUrl}[\\s]*\\)`,
3523
3929
  "g"
3524
3930
  );
3525
- cleanedContent = cleanedContent.replace(markdownImagePattern, placeholder);
3526
- cleanedContent = cleanedContent.replace(new RegExp(escapedUrl, "g"), placeholder);
3931
+ const markdownMatches = cleanedContent.match(markdownImagePattern);
3932
+ if (markdownMatches) {
3933
+ console.log(
3934
+ `[extractAndStoreEncryptedMCPImages] Replacing ${markdownMatches.length} markdown image(s):`,
3935
+ markdownMatches,
3936
+ "->",
3937
+ placeholder
3938
+ );
3939
+ replacementCount += markdownMatches.length;
3940
+ cleanedContent = cleanedContent.replace(
3941
+ markdownImagePattern,
3942
+ placeholder
3943
+ );
3944
+ }
3945
+ const rawUrlPattern = new RegExp(escapedUrl, "g");
3946
+ const rawMatches = cleanedContent.match(rawUrlPattern);
3947
+ if (rawMatches) {
3948
+ console.log(
3949
+ `[extractAndStoreEncryptedMCPImages] Replacing ${rawMatches.length} raw URL(s):`,
3950
+ rawMatches,
3951
+ "->",
3952
+ placeholder
3953
+ );
3954
+ replacementCount += rawMatches.length;
3955
+ cleanedContent = cleanedContent.replace(rawUrlPattern, placeholder);
3956
+ }
3957
+ console.log(
3958
+ `[extractAndStoreEncryptedMCPImages] Total replacements made: ${replacementCount} for URL:`,
3959
+ imageUrl
3960
+ );
3961
+ const expectedCount = urlOccurrenceCounts.get(imageUrl) || 0;
3962
+ if (replacementCount < expectedCount) {
3963
+ console.warn(
3964
+ `[extractAndStoreEncryptedMCPImages] Not all instances of URL replaced. Expected ${expectedCount}, replaced ${replacementCount}:`,
3965
+ imageUrl
3966
+ );
3967
+ }
3968
+ const remainingPattern = new RegExp(escapedUrl, "g");
3969
+ const remainingMatches = cleanedContent.match(remainingPattern);
3970
+ if (remainingMatches && remainingMatches.length > 0) {
3971
+ console.warn(
3972
+ `[extractAndStoreEncryptedMCPImages] Found ${remainingMatches.length} remaining instance(s) of URL after replacement:`,
3973
+ imageUrl
3974
+ );
3975
+ }
3527
3976
  } else {
3528
3977
  console.error("[extractAndStoreEncryptedMCPImages] Failed:", result.reason);
3529
3978
  }
@@ -3588,9 +4037,7 @@ function useChatStorage(options) {
3588
4037
  const storedMessages = await getMessagesOp(storageCtx, convId);
3589
4038
  const validMessages = storedMessages.filter((msg) => !msg.error);
3590
4039
  const limitedMessages = validMessages.slice(-maxHistoryMessages);
3591
- const historyMessages = await Promise.all(
3592
- limitedMessages.map(storedToLlmapiMessage)
3593
- );
4040
+ const historyMessages = limitedMessages.map(storedToLlmapiMessage);
3594
4041
  messagesToSend = [...historyMessages, ...messages];
3595
4042
  } else {
3596
4043
  messagesToSend = [...messages];