@reverbia/sdk 1.0.0-next.20260110224403 → 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,19 +3168,70 @@ 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) {
@@ -3035,7 +3241,7 @@ function storedToLlmapiMessage(stored) {
3035
3241
  let textContent = stored.content;
3036
3242
  const fileUrlMap = /* @__PURE__ */ new Map();
3037
3243
  const imageParts = [];
3038
- if (stored.files?.length) {
3244
+ if (stored.role !== "assistant" && stored.files?.length) {
3039
3245
  for (const file of stored.files) {
3040
3246
  if (file.url) {
3041
3247
  imageParts.push({
@@ -3050,6 +3256,12 @@ function storedToLlmapiMessage(stored) {
3050
3256
  fileUrlMap.set(file.id, file.sourceUrl);
3051
3257
  }
3052
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);
3263
+ }
3264
+ }
3053
3265
  }
3054
3266
  textContent = textContent.replace(
3055
3267
  /__SDKFILE__([a-f0-9-]+)__/g,
@@ -3181,26 +3393,63 @@ function useChatStorage(options) {
3181
3393
  if (fileIds.length === 0) {
3182
3394
  return msg;
3183
3395
  }
3396
+ console.log(
3397
+ `[getMessages] Found ${fileIds.length} placeholder(s) in message ${msg.uniqueId}:`,
3398
+ fileIds
3399
+ );
3184
3400
  let resolvedContent = msg.content;
3185
3401
  for (const fileId of fileIds) {
3402
+ const placeholder = createFilePlaceholder(fileId);
3403
+ console.log(
3404
+ `[getMessages] Resolving placeholder: ${placeholder} (fileId: ${fileId})`
3405
+ );
3186
3406
  let url = blobManager.getUrl(fileId);
3187
3407
  if (!url) {
3408
+ console.log(
3409
+ `[getMessages] No cached URL for ${fileId}, reading from OPFS...`
3410
+ );
3188
3411
  const result = await readEncryptedFile(fileId, encryptionKey);
3189
3412
  if (result) {
3190
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
+ );
3191
3422
  }
3423
+ } else {
3424
+ console.log(
3425
+ `[getMessages] Using cached blob URL for ${fileId}:`,
3426
+ url
3427
+ );
3192
3428
  }
3193
3429
  if (url) {
3194
- 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
+ );
3195
3440
  resolvedContent = resolvedContent.replace(
3196
- new RegExp(
3197
- placeholder.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"),
3198
- "g"
3199
- ),
3200
- `![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`
3201
3447
  );
3202
3448
  }
3203
3449
  }
3450
+ console.log(
3451
+ `[getMessages] Resolved content length: ${resolvedContent.length}, original length: ${msg.content.length}`
3452
+ );
3204
3453
  return { ...msg, content: resolvedContent };
3205
3454
  })
3206
3455
  );
@@ -3340,16 +3589,42 @@ function useChatStorage(options) {
3340
3589
  const { replaceUrls = true } = options2 ?? {};
3341
3590
  try {
3342
3591
  const MCP_IMAGE_URL_PATTERN = new RegExp(
3343
- `https://${MCP_R2_DOMAIN.replace(/\./g, "\\.")}[^\\s)]*`,
3592
+ `https://${MCP_R2_DOMAIN.replace(/\./g, "\\.")}[^\\s"'<>)]*`,
3344
3593
  "g"
3345
3594
  );
3346
3595
  const urlMatches = content.match(MCP_IMAGE_URL_PATTERN);
3347
3596
  if (!urlMatches || urlMatches.length === 0) {
3348
3597
  return { processedFiles: [], cleanedContent: content };
3349
3598
  }
3350
- const uniqueUrls = [...new Set(urlMatches)];
3599
+ const cleanedUrls = urlMatches.map((url) => url.replace(/["']+$/, ""));
3600
+ const uniqueUrls = [...new Set(cleanedUrls)];
3351
3601
  const processedFiles = [];
3352
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
+ });
3353
3628
  const results = await Promise.allSettled(
3354
3629
  uniqueUrls.map(async (imageUrl) => {
3355
3630
  const controller = new AbortController();
@@ -3381,27 +3656,85 @@ function useChatStorage(options) {
3381
3656
  );
3382
3657
  const replaceUrlWithPlaceholder = (imageUrl, fileId) => {
3383
3658
  const escapedUrl = imageUrl.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
3384
- const placeholder = `![MCP_IMAGE:${fileId}]`;
3385
- const markdownImagePattern = new RegExp(
3386
- `!\\[[^\\]]*\\]\\([\\s]*${escapedUrl}[\\s]*\\)`,
3387
- "g"
3388
- );
3389
- cleanedContent = cleanedContent.replace(
3390
- markdownImagePattern,
3659
+ const placeholder = createFilePlaceholder(fileId);
3660
+ let replacementCount = 0;
3661
+ console.log(
3662
+ `[extractAndStoreMCPImages] Replacing URL with placeholder:`,
3663
+ imageUrl,
3664
+ "->",
3391
3665
  placeholder
3392
3666
  );
3393
- cleanedContent = cleanedContent.replace(
3394
- new RegExp(escapedUrl, "g"),
3395
- placeholder
3667
+ const htmlImgPatternDouble = new RegExp(
3668
+ `<img[^>]*src="${escapedUrl}"[^>]*>`,
3669
+ "gi"
3670
+ );
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"
3396
3688
  );
3397
- const orphanedMarkdownPattern = new RegExp(
3398
- `!\\[[^\\]]*\\]\\([\\s]*\\!\\[MCP_IMAGE:${fileId}\\][\\s]*\\)`,
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]*\\)`,
3399
3705
  "g"
3400
3706
  );
3401
- cleanedContent = cleanedContent.replace(
3402
- orphanedMarkdownPattern,
3403
- 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
3404
3736
  );
3737
+ return replacementCount;
3405
3738
  };
3406
3739
  results.forEach((result, i) => {
3407
3740
  const imageUrl = uniqueUrls[i];
@@ -3415,7 +3748,29 @@ function useChatStorage(options) {
3415
3748
  sourceUrl: imageUrl
3416
3749
  });
3417
3750
  if (replaceUrls && imageUrl) {
3418
- 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
+ }
3419
3774
  }
3420
3775
  } else {
3421
3776
  console.error(
@@ -3448,17 +3803,43 @@ function useChatStorage(options) {
3448
3803
  return { processedFiles: [], cleanedContent: content };
3449
3804
  }
3450
3805
  const MCP_IMAGE_URL_PATTERN = new RegExp(
3451
- `https://${MCP_R2_DOMAIN.replace(/\./g, "\\.")}[^\\s)]*`,
3806
+ `https://${MCP_R2_DOMAIN.replace(/\./g, "\\.")}[^\\s"'<>)]*`,
3452
3807
  "g"
3453
3808
  );
3454
3809
  const urlMatches = content.match(MCP_IMAGE_URL_PATTERN);
3455
3810
  if (!urlMatches || urlMatches.length === 0) {
3456
3811
  return { processedFiles: [], cleanedContent: content };
3457
3812
  }
3458
- const uniqueUrls = [...new Set(urlMatches)];
3813
+ const cleanedUrls = urlMatches.map((url) => url.replace(/["']+$/, ""));
3814
+ const uniqueUrls = [...new Set(cleanedUrls)];
3459
3815
  const encryptionKey = await getEncryptionKey(address);
3460
3816
  const processedFiles = [];
3461
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
+ });
3462
3843
  const results = await Promise.allSettled(
3463
3844
  uniqueUrls.map(async (imageUrl) => {
3464
3845
  const controller = new AbortController();
@@ -3500,12 +3881,98 @@ function useChatStorage(options) {
3500
3881
  });
3501
3882
  const placeholder = createFilePlaceholder(fileId);
3502
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
+ }
3503
3927
  const markdownImagePattern = new RegExp(
3504
3928
  `!\\[[^\\]]*\\]\\([\\s]*${escapedUrl}[\\s]*\\)`,
3505
3929
  "g"
3506
3930
  );
3507
- cleanedContent = cleanedContent.replace(markdownImagePattern, placeholder);
3508
- 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
+ }
3509
3976
  } else {
3510
3977
  console.error("[extractAndStoreEncryptedMCPImages] Failed:", result.reason);
3511
3978
  }