@reverbia/sdk 1.0.0-next.20260110224403 → 1.0.0-next.20260111125102

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.
@@ -875,14 +875,128 @@ function validateToken(token) {
875
875
  }
876
876
  return { valid: true };
877
877
  }
878
- function createStreamAccumulator() {
878
+ function parseReasoningTags(content, previousPartialTag = "") {
879
+ const OPENING_TAG = "<think>";
880
+ const CLOSING_TAG = "</think>";
881
+ const OPENING_TAG_LEN = OPENING_TAG.length;
882
+ const CLOSING_TAG_LEN = CLOSING_TAG.length;
883
+ const fullContent = previousPartialTag + content;
884
+ let messageContent = "";
885
+ let reasoningContent = "";
886
+ let partialTag = "";
887
+ let i = 0;
888
+ let insideReasoning = false;
889
+ if (previousPartialTag) {
890
+ if (previousPartialTag === OPENING_TAG) {
891
+ insideReasoning = true;
892
+ i = OPENING_TAG_LEN;
893
+ } else if (OPENING_TAG.startsWith(previousPartialTag)) {
894
+ if (fullContent.startsWith(OPENING_TAG)) {
895
+ insideReasoning = true;
896
+ i = OPENING_TAG_LEN;
897
+ } else if (OPENING_TAG.startsWith(fullContent.slice(0, Math.min(OPENING_TAG_LEN, fullContent.length)))) {
898
+ return {
899
+ messageContent: "",
900
+ reasoningContent: "",
901
+ partialTag: fullContent.slice(0, Math.min(OPENING_TAG_LEN, fullContent.length))
902
+ };
903
+ } else {
904
+ messageContent = previousPartialTag;
905
+ i = previousPartialTag.length;
906
+ }
907
+ } else if (CLOSING_TAG.startsWith(previousPartialTag)) {
908
+ if (fullContent.startsWith(CLOSING_TAG)) {
909
+ i = CLOSING_TAG_LEN;
910
+ insideReasoning = false;
911
+ } else if (CLOSING_TAG.startsWith(fullContent.slice(0, Math.min(CLOSING_TAG_LEN, fullContent.length)))) {
912
+ return {
913
+ messageContent: "",
914
+ reasoningContent: "",
915
+ partialTag: fullContent.slice(0, Math.min(CLOSING_TAG_LEN, fullContent.length))
916
+ };
917
+ } else {
918
+ reasoningContent = previousPartialTag;
919
+ i = previousPartialTag.length;
920
+ insideReasoning = true;
921
+ }
922
+ } else {
923
+ if (insideReasoning) {
924
+ reasoningContent = previousPartialTag;
925
+ } else {
926
+ messageContent = previousPartialTag;
927
+ }
928
+ i = previousPartialTag.length;
929
+ }
930
+ }
931
+ while (i < fullContent.length) {
932
+ if (insideReasoning) {
933
+ const closeIndex = fullContent.indexOf(CLOSING_TAG, i);
934
+ if (closeIndex === -1) {
935
+ const remaining = fullContent.slice(i);
936
+ if (remaining.length < CLOSING_TAG_LEN) {
937
+ const potentialClose = remaining;
938
+ if (CLOSING_TAG.startsWith(potentialClose)) {
939
+ partialTag = potentialClose;
940
+ } else {
941
+ reasoningContent += remaining;
942
+ }
943
+ } else {
944
+ reasoningContent += remaining;
945
+ }
946
+ break;
947
+ }
948
+ const contentBeforeClose = fullContent.slice(i, closeIndex);
949
+ if (contentBeforeClose) {
950
+ reasoningContent += contentBeforeClose;
951
+ }
952
+ i = closeIndex + CLOSING_TAG_LEN;
953
+ insideReasoning = false;
954
+ } else {
955
+ const openIndex = fullContent.indexOf(OPENING_TAG, i);
956
+ if (openIndex === -1) {
957
+ const remaining = fullContent.slice(i);
958
+ if (remaining.length < OPENING_TAG_LEN) {
959
+ const potentialOpen = remaining;
960
+ if (OPENING_TAG.startsWith(potentialOpen)) {
961
+ partialTag = potentialOpen;
962
+ } else {
963
+ messageContent += remaining;
964
+ }
965
+ } else {
966
+ messageContent += remaining;
967
+ }
968
+ break;
969
+ }
970
+ messageContent += fullContent.slice(i, openIndex);
971
+ i = openIndex + OPENING_TAG_LEN;
972
+ insideReasoning = true;
973
+ }
974
+ }
975
+ if (messageContent.includes(OPENING_TAG) || messageContent.includes(CLOSING_TAG)) {
976
+ console.warn("[parseReasoningTags] Warning: Tag found in messageContent, removing");
977
+ messageContent = messageContent.replace(new RegExp(OPENING_TAG.replace(/[<>]/g, "\\$&"), "g"), "");
978
+ messageContent = messageContent.replace(new RegExp(CLOSING_TAG.replace(/[<>]/g, "\\$&"), "g"), "");
979
+ }
980
+ if (reasoningContent.includes(OPENING_TAG) || reasoningContent.includes(CLOSING_TAG)) {
981
+ console.warn("[parseReasoningTags] Warning: Tag found in reasoningContent, removing");
982
+ reasoningContent = reasoningContent.replace(new RegExp(OPENING_TAG.replace(/[<>]/g, "\\$&"), "g"), "");
983
+ reasoningContent = reasoningContent.replace(new RegExp(CLOSING_TAG.replace(/[<>]/g, "\\$&"), "g"), "");
984
+ }
985
+ return {
986
+ messageContent,
987
+ reasoningContent,
988
+ partialTag
989
+ };
990
+ }
991
+ function createStreamAccumulator(initialModel) {
879
992
  return {
880
993
  content: "",
881
994
  thinking: "",
882
995
  responseId: "",
883
- responseModel: "",
996
+ responseModel: initialModel || "",
884
997
  usage: {},
885
- toolCalls: /* @__PURE__ */ new Map()
998
+ toolCalls: /* @__PURE__ */ new Map(),
999
+ partialReasoningTag: ""
886
1000
  };
887
1001
  }
888
1002
  function createErrorResult(message, onError) {
@@ -1052,7 +1166,7 @@ var ResponsesStrategy = class {
1052
1166
  const delta = typedChunk.delta;
1053
1167
  if (delta) {
1054
1168
  const deltaText = typeof delta === "string" ? delta : delta.OfString;
1055
- if (deltaText) {
1169
+ if (deltaText && deltaText.trim().length > 0) {
1056
1170
  accumulator.content += deltaText;
1057
1171
  result.content = deltaText;
1058
1172
  }
@@ -1179,8 +1293,21 @@ var CompletionsStrategy = class {
1179
1293
  const choice = typedChunk.choices[0];
1180
1294
  if (choice.delta) {
1181
1295
  if (choice.delta.content) {
1182
- accumulator.content += choice.delta.content;
1183
- result.content = choice.delta.content;
1296
+ const parseResult = parseReasoningTags(
1297
+ choice.delta.content,
1298
+ accumulator.partialReasoningTag || ""
1299
+ );
1300
+ accumulator.content += parseResult.messageContent;
1301
+ accumulator.thinking += parseResult.reasoningContent;
1302
+ accumulator.partialReasoningTag = parseResult.partialTag;
1303
+ const willEmitMessage = parseResult.messageContent && parseResult.messageContent.trim().length > 0;
1304
+ const willEmitReasoning = parseResult.reasoningContent && parseResult.reasoningContent.trim().length > 0;
1305
+ if (willEmitMessage) {
1306
+ result.content = parseResult.messageContent;
1307
+ }
1308
+ if (willEmitReasoning) {
1309
+ result.thinking = parseResult.reasoningContent;
1310
+ }
1184
1311
  }
1185
1312
  if (choice.delta.tool_calls) {
1186
1313
  for (const toolCallDelta of choice.delta.tool_calls) {
@@ -1209,8 +1336,19 @@ var CompletionsStrategy = class {
1209
1336
  }
1210
1337
  if (choice.message) {
1211
1338
  if (choice.message.content) {
1212
- accumulator.content = choice.message.content;
1213
- result.content = choice.message.content;
1339
+ const parseResult = parseReasoningTags(
1340
+ choice.message.content,
1341
+ accumulator.partialReasoningTag || ""
1342
+ );
1343
+ accumulator.content = parseResult.messageContent;
1344
+ accumulator.thinking += parseResult.reasoningContent;
1345
+ accumulator.partialReasoningTag = parseResult.partialTag;
1346
+ if (parseResult.messageContent && parseResult.messageContent.trim().length > 0) {
1347
+ result.content = parseResult.messageContent;
1348
+ }
1349
+ if (parseResult.reasoningContent && parseResult.reasoningContent.trim().length > 0) {
1350
+ result.thinking = parseResult.reasoningContent;
1351
+ }
1214
1352
  }
1215
1353
  if (choice.message.tool_calls) {
1216
1354
  for (let i = 0; i < choice.message.tool_calls.length; i++) {
@@ -1238,6 +1376,23 @@ var CompletionsStrategy = class {
1238
1376
  }
1239
1377
  buildFinalResponse(accumulator) {
1240
1378
  const output = [];
1379
+ let finalContent = accumulator.content;
1380
+ let finalThinking = accumulator.thinking;
1381
+ if (accumulator.partialReasoningTag) {
1382
+ const finalParse = parseReasoningTags("", accumulator.partialReasoningTag);
1383
+ finalContent += finalParse.messageContent;
1384
+ if (finalParse.reasoningContent) {
1385
+ finalThinking += finalParse.reasoningContent;
1386
+ }
1387
+ }
1388
+ if (finalThinking) {
1389
+ output.push({
1390
+ type: "reasoning",
1391
+ role: "assistant",
1392
+ content: [{ type: "output_text", text: finalThinking }],
1393
+ status: "completed"
1394
+ });
1395
+ }
1241
1396
  if (accumulator.toolCalls.size > 0) {
1242
1397
  for (const toolCall of accumulator.toolCalls.values()) {
1243
1398
  output.push({
@@ -1252,7 +1407,7 @@ var CompletionsStrategy = class {
1252
1407
  output.push({
1253
1408
  type: "message",
1254
1409
  role: "assistant",
1255
- content: [{ type: "output_text", text: accumulator.content }],
1410
+ content: [{ type: "output_text", text: finalContent }],
1256
1411
  status: "completed"
1257
1412
  });
1258
1413
  return {
@@ -1432,7 +1587,7 @@ function useChat(options) {
1432
1587
  sseError = error instanceof Error ? error : new Error(String(error));
1433
1588
  }
1434
1589
  });
1435
- const accumulator = createStreamAccumulator();
1590
+ const accumulator = createStreamAccumulator(model || void 0);
1436
1591
  try {
1437
1592
  for await (const chunk of sseResult.stream) {
1438
1593
  if (isDoneMarker(chunk)) {
@@ -1627,7 +1782,7 @@ Executing tool: ${toolInfo}
1627
1782
  sseError = error instanceof Error ? error : new Error(String(error));
1628
1783
  }
1629
1784
  });
1630
- const continuationAccumulator = createStreamAccumulator();
1785
+ const continuationAccumulator = createStreamAccumulator(model || void 0);
1631
1786
  try {
1632
1787
  for await (const chunk of continuationResult.stream) {
1633
1788
  if (isDoneMarker(chunk)) {
@@ -2877,19 +3032,70 @@ var BlobUrlManager = class {
2877
3032
  // src/react/useChatStorage.ts
2878
3033
  function replaceUrlWithMCPPlaceholder(content, url, fileId) {
2879
3034
  const escapedUrl = url.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
2880
- const placeholder = `![MCP_IMAGE:${fileId}]`;
3035
+ const placeholder = createFilePlaceholder(fileId);
2881
3036
  let result = content;
3037
+ console.log(
3038
+ `[replaceUrlWithMCPPlaceholder] Replacing URL with placeholder:`,
3039
+ url,
3040
+ "->",
3041
+ placeholder
3042
+ );
3043
+ const htmlImgPatternDouble = new RegExp(
3044
+ `<img[^>]*src="${escapedUrl}"[^>]*>`,
3045
+ "gi"
3046
+ );
3047
+ const doubleMatches = result.match(htmlImgPatternDouble);
3048
+ if (doubleMatches) {
3049
+ console.log(
3050
+ `[replaceUrlWithMCPPlaceholder] Replacing ${doubleMatches.length} HTML img tag(s) with double quotes:`,
3051
+ doubleMatches,
3052
+ "->",
3053
+ placeholder
3054
+ );
3055
+ }
3056
+ result = result.replace(htmlImgPatternDouble, placeholder);
3057
+ const htmlImgPatternSingle = new RegExp(
3058
+ `<img[^>]*src='${escapedUrl}'[^>]*>`,
3059
+ "gi"
3060
+ );
3061
+ const singleMatches = result.match(htmlImgPatternSingle);
3062
+ if (singleMatches) {
3063
+ console.log(
3064
+ `[replaceUrlWithMCPPlaceholder] Replacing ${singleMatches.length} HTML img tag(s) with single quotes:`,
3065
+ singleMatches,
3066
+ "->",
3067
+ placeholder
3068
+ );
3069
+ }
3070
+ result = result.replace(htmlImgPatternSingle, placeholder);
2882
3071
  const markdownImagePattern = new RegExp(
2883
3072
  `!\\[[^\\]]*\\]\\([\\s]*${escapedUrl}[\\s]*\\)`,
2884
3073
  "g"
2885
3074
  );
3075
+ const markdownMatches = result.match(markdownImagePattern);
3076
+ if (markdownMatches) {
3077
+ console.log(
3078
+ `[replaceUrlWithMCPPlaceholder] Replacing ${markdownMatches.length} markdown image(s):`,
3079
+ markdownMatches,
3080
+ "->",
3081
+ placeholder
3082
+ );
3083
+ }
2886
3084
  result = result.replace(markdownImagePattern, placeholder);
2887
- result = result.replace(new RegExp(escapedUrl, "g"), placeholder);
2888
- const orphanedMarkdownPattern = new RegExp(
2889
- `!\\[[^\\]]*\\]\\([\\s]*\\!\\[MCP_IMAGE:${fileId}\\][\\s]*\\)`,
2890
- "g"
3085
+ const rawUrlPattern = new RegExp(escapedUrl, "g");
3086
+ const rawMatches = result.match(rawUrlPattern);
3087
+ if (rawMatches) {
3088
+ console.log(
3089
+ `[replaceUrlWithMCPPlaceholder] Replacing ${rawMatches.length} raw URL(s):`,
3090
+ rawMatches,
3091
+ "->",
3092
+ placeholder
3093
+ );
3094
+ }
3095
+ result = result.replace(rawUrlPattern, placeholder);
3096
+ console.log(
3097
+ `[replaceUrlWithMCPPlaceholder] Final result length: ${result.length}, original length: ${content.length}`
2891
3098
  );
2892
- result = result.replace(orphanedMarkdownPattern, placeholder);
2893
3099
  return result;
2894
3100
  }
2895
3101
  function findFileIdBySourceUrl(files, sourceUrl) {
@@ -2899,7 +3105,7 @@ function storedToLlmapiMessage(stored) {
2899
3105
  let textContent = stored.content;
2900
3106
  const fileUrlMap = /* @__PURE__ */ new Map();
2901
3107
  const imageParts = [];
2902
- if (stored.files?.length) {
3108
+ if (stored.role !== "assistant" && stored.files?.length) {
2903
3109
  for (const file of stored.files) {
2904
3110
  if (file.url) {
2905
3111
  imageParts.push({
@@ -2914,6 +3120,12 @@ function storedToLlmapiMessage(stored) {
2914
3120
  fileUrlMap.set(file.id, file.sourceUrl);
2915
3121
  }
2916
3122
  }
3123
+ } else if (stored.role === "assistant" && stored.files?.length) {
3124
+ for (const file of stored.files) {
3125
+ if (file.sourceUrl) {
3126
+ fileUrlMap.set(file.id, file.sourceUrl);
3127
+ }
3128
+ }
2917
3129
  }
2918
3130
  textContent = textContent.replace(
2919
3131
  /__SDKFILE__([a-f0-9-]+)__/g,
@@ -3041,30 +3253,67 @@ function useChatStorage(options) {
3041
3253
  const blobManager = blobManagerRef.current;
3042
3254
  const resolvedMessages = await Promise.all(
3043
3255
  messages.map(async (msg) => {
3044
- const fileIds = extractFileIds(msg.content);
3256
+ const fileIds = [...new Set(extractFileIds(msg.content))];
3045
3257
  if (fileIds.length === 0) {
3046
3258
  return msg;
3047
3259
  }
3260
+ console.log(
3261
+ `[getMessages] Found ${fileIds.length} placeholder(s) in message ${msg.uniqueId}:`,
3262
+ fileIds
3263
+ );
3048
3264
  let resolvedContent = msg.content;
3049
3265
  for (const fileId of fileIds) {
3266
+ const placeholder = createFilePlaceholder(fileId);
3267
+ console.log(
3268
+ `[getMessages] Resolving placeholder: ${placeholder} (fileId: ${fileId})`
3269
+ );
3050
3270
  let url = blobManager.getUrl(fileId);
3051
3271
  if (!url) {
3272
+ console.log(
3273
+ `[getMessages] No cached URL for ${fileId}, reading from OPFS...`
3274
+ );
3052
3275
  const result = await readEncryptedFile(fileId, encryptionKey);
3053
3276
  if (result) {
3054
3277
  url = blobManager.createUrl(fileId, result.blob);
3278
+ console.log(
3279
+ `[getMessages] Created blob URL for ${fileId}:`,
3280
+ url
3281
+ );
3282
+ } else {
3283
+ console.warn(
3284
+ `[getMessages] Failed to read file ${fileId} from OPFS`
3285
+ );
3055
3286
  }
3287
+ } else {
3288
+ console.log(
3289
+ `[getMessages] Using cached blob URL for ${fileId}:`,
3290
+ url
3291
+ );
3056
3292
  }
3057
3293
  if (url) {
3058
- const placeholder = createFilePlaceholder(fileId);
3294
+ const placeholderRegex = new RegExp(
3295
+ placeholder.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"),
3296
+ "g"
3297
+ );
3298
+ const matches = resolvedContent.match(placeholderRegex);
3299
+ const replacement = `![image](${url})`;
3300
+ console.log(
3301
+ `[getMessages] Replacing ${matches?.length || 0} instance(s) of ${placeholder} with:`,
3302
+ replacement
3303
+ );
3059
3304
  resolvedContent = resolvedContent.replace(
3060
- new RegExp(
3061
- placeholder.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"),
3062
- "g"
3063
- ),
3064
- `![image](${url})`
3305
+ placeholderRegex,
3306
+ replacement
3307
+ );
3308
+ } else {
3309
+ console.warn(
3310
+ `[getMessages] No URL available for ${fileId}, placeholder ${placeholder} will remain in content`
3065
3311
  );
3066
3312
  }
3067
3313
  }
3314
+ console.log(
3315
+ `[getMessages] Resolved content length: ${resolvedContent.length}, original length: ${msg.content.length}`
3316
+ );
3068
3317
  return { ...msg, content: resolvedContent };
3069
3318
  })
3070
3319
  );
@@ -3204,16 +3453,42 @@ function useChatStorage(options) {
3204
3453
  const { replaceUrls = true } = options2 ?? {};
3205
3454
  try {
3206
3455
  const MCP_IMAGE_URL_PATTERN = new RegExp(
3207
- `https://${MCP_R2_DOMAIN.replace(/\./g, "\\.")}[^\\s)]*`,
3456
+ `https://${MCP_R2_DOMAIN.replace(/\./g, "\\.")}[^\\s"'<>)]*`,
3208
3457
  "g"
3209
3458
  );
3210
3459
  const urlMatches = content.match(MCP_IMAGE_URL_PATTERN);
3211
3460
  if (!urlMatches || urlMatches.length === 0) {
3212
3461
  return { processedFiles: [], cleanedContent: content };
3213
3462
  }
3214
- const uniqueUrls = [...new Set(urlMatches)];
3463
+ const cleanedUrls = urlMatches.map((url) => url.replace(/["']+$/, ""));
3464
+ const uniqueUrls = [...new Set(cleanedUrls)];
3215
3465
  const processedFiles = [];
3216
3466
  let cleanedContent = content;
3467
+ const urlOccurrenceCounts = /* @__PURE__ */ new Map();
3468
+ uniqueUrls.forEach((url) => {
3469
+ const escapedUrl = url.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
3470
+ const htmlDoublePattern = new RegExp(
3471
+ `<img[^>]*src="${escapedUrl}"[^>]*>`,
3472
+ "gi"
3473
+ );
3474
+ const htmlSinglePattern = new RegExp(
3475
+ `<img[^>]*src='${escapedUrl}'[^>]*>`,
3476
+ "gi"
3477
+ );
3478
+ const markdownPattern = new RegExp(
3479
+ `!\\[[^\\]]*\\]\\([\\s]*${escapedUrl}[\\s]*\\)`,
3480
+ "g"
3481
+ );
3482
+ const rawPattern = new RegExp(escapedUrl, "g");
3483
+ const htmlDoubleMatches = content.match(htmlDoublePattern)?.length || 0;
3484
+ const htmlSingleMatches = content.match(htmlSinglePattern)?.length || 0;
3485
+ const markdownMatches = content.match(markdownPattern)?.length || 0;
3486
+ const rawMatches = (content.match(rawPattern)?.length || 0) - htmlDoubleMatches - htmlSingleMatches - markdownMatches;
3487
+ urlOccurrenceCounts.set(
3488
+ url,
3489
+ htmlDoubleMatches + htmlSingleMatches + markdownMatches + rawMatches
3490
+ );
3491
+ });
3217
3492
  const results = await Promise.allSettled(
3218
3493
  uniqueUrls.map(async (imageUrl) => {
3219
3494
  const controller = new AbortController();
@@ -3245,27 +3520,85 @@ function useChatStorage(options) {
3245
3520
  );
3246
3521
  const replaceUrlWithPlaceholder = (imageUrl, fileId) => {
3247
3522
  const escapedUrl = imageUrl.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
3248
- const placeholder = `![MCP_IMAGE:${fileId}]`;
3249
- const markdownImagePattern = new RegExp(
3250
- `!\\[[^\\]]*\\]\\([\\s]*${escapedUrl}[\\s]*\\)`,
3251
- "g"
3252
- );
3253
- cleanedContent = cleanedContent.replace(
3254
- markdownImagePattern,
3523
+ const placeholder = createFilePlaceholder(fileId);
3524
+ let replacementCount = 0;
3525
+ console.log(
3526
+ `[extractAndStoreMCPImages] Replacing URL with placeholder:`,
3527
+ imageUrl,
3528
+ "->",
3255
3529
  placeholder
3256
3530
  );
3257
- cleanedContent = cleanedContent.replace(
3258
- new RegExp(escapedUrl, "g"),
3259
- placeholder
3531
+ const htmlImgPatternDouble = new RegExp(
3532
+ `<img[^>]*src="${escapedUrl}"[^>]*>`,
3533
+ "gi"
3534
+ );
3535
+ const doubleMatches = cleanedContent.match(htmlImgPatternDouble);
3536
+ if (doubleMatches) {
3537
+ console.log(
3538
+ `[extractAndStoreMCPImages] Replacing ${doubleMatches.length} HTML img tag(s) with double quotes:`,
3539
+ doubleMatches,
3540
+ "->",
3541
+ placeholder
3542
+ );
3543
+ replacementCount += doubleMatches.length;
3544
+ cleanedContent = cleanedContent.replace(
3545
+ htmlImgPatternDouble,
3546
+ placeholder
3547
+ );
3548
+ }
3549
+ const htmlImgPatternSingle = new RegExp(
3550
+ `<img[^>]*src='${escapedUrl}'[^>]*>`,
3551
+ "gi"
3260
3552
  );
3261
- const orphanedMarkdownPattern = new RegExp(
3262
- `!\\[[^\\]]*\\]\\([\\s]*\\!\\[MCP_IMAGE:${fileId}\\][\\s]*\\)`,
3553
+ const singleMatches = cleanedContent.match(htmlImgPatternSingle);
3554
+ if (singleMatches) {
3555
+ console.log(
3556
+ `[extractAndStoreMCPImages] Replacing ${singleMatches.length} HTML img tag(s) with single quotes:`,
3557
+ singleMatches,
3558
+ "->",
3559
+ placeholder
3560
+ );
3561
+ replacementCount += singleMatches.length;
3562
+ cleanedContent = cleanedContent.replace(
3563
+ htmlImgPatternSingle,
3564
+ placeholder
3565
+ );
3566
+ }
3567
+ const markdownImagePattern = new RegExp(
3568
+ `!\\[[^\\]]*\\]\\([\\s]*${escapedUrl}[\\s]*\\)`,
3263
3569
  "g"
3264
3570
  );
3265
- cleanedContent = cleanedContent.replace(
3266
- orphanedMarkdownPattern,
3267
- placeholder
3571
+ const markdownMatches = cleanedContent.match(markdownImagePattern);
3572
+ if (markdownMatches) {
3573
+ console.log(
3574
+ `[extractAndStoreMCPImages] Replacing ${markdownMatches.length} markdown image(s):`,
3575
+ markdownMatches,
3576
+ "->",
3577
+ placeholder
3578
+ );
3579
+ replacementCount += markdownMatches.length;
3580
+ cleanedContent = cleanedContent.replace(
3581
+ markdownImagePattern,
3582
+ placeholder
3583
+ );
3584
+ }
3585
+ const rawUrlPattern = new RegExp(escapedUrl, "g");
3586
+ const rawMatches = cleanedContent.match(rawUrlPattern);
3587
+ if (rawMatches) {
3588
+ console.log(
3589
+ `[extractAndStoreMCPImages] Replacing ${rawMatches.length} raw URL(s):`,
3590
+ rawMatches,
3591
+ "->",
3592
+ placeholder
3593
+ );
3594
+ replacementCount += rawMatches.length;
3595
+ cleanedContent = cleanedContent.replace(rawUrlPattern, placeholder);
3596
+ }
3597
+ console.log(
3598
+ `[extractAndStoreMCPImages] Total replacements made: ${replacementCount} for URL:`,
3599
+ imageUrl
3268
3600
  );
3601
+ return replacementCount;
3269
3602
  };
3270
3603
  results.forEach((result, i) => {
3271
3604
  const imageUrl = uniqueUrls[i];
@@ -3279,7 +3612,29 @@ function useChatStorage(options) {
3279
3612
  sourceUrl: imageUrl
3280
3613
  });
3281
3614
  if (replaceUrls && imageUrl) {
3282
- replaceUrlWithPlaceholder(imageUrl, fileId);
3615
+ const replacementCount = replaceUrlWithPlaceholder(
3616
+ imageUrl,
3617
+ fileId
3618
+ );
3619
+ const expectedCount = urlOccurrenceCounts.get(imageUrl) || 0;
3620
+ if (replacementCount < expectedCount) {
3621
+ console.warn(
3622
+ `[extractAndStoreMCPImages] Not all instances of URL replaced. Expected ${expectedCount}, replaced ${replacementCount}:`,
3623
+ imageUrl
3624
+ );
3625
+ }
3626
+ const escapedUrl = imageUrl.replace(
3627
+ /[.*+?^${}()|[\]\\]/g,
3628
+ "\\$&"
3629
+ );
3630
+ const remainingPattern = new RegExp(escapedUrl, "g");
3631
+ const remainingMatches = cleanedContent.match(remainingPattern);
3632
+ if (remainingMatches && remainingMatches.length > 0) {
3633
+ console.warn(
3634
+ `[extractAndStoreMCPImages] Found ${remainingMatches.length} remaining instance(s) of URL after replacement:`,
3635
+ imageUrl
3636
+ );
3637
+ }
3283
3638
  }
3284
3639
  } else {
3285
3640
  console.error(
@@ -3312,17 +3667,43 @@ function useChatStorage(options) {
3312
3667
  return { processedFiles: [], cleanedContent: content };
3313
3668
  }
3314
3669
  const MCP_IMAGE_URL_PATTERN = new RegExp(
3315
- `https://${MCP_R2_DOMAIN.replace(/\./g, "\\.")}[^\\s)]*`,
3670
+ `https://${MCP_R2_DOMAIN.replace(/\./g, "\\.")}[^\\s"'<>)]*`,
3316
3671
  "g"
3317
3672
  );
3318
3673
  const urlMatches = content.match(MCP_IMAGE_URL_PATTERN);
3319
3674
  if (!urlMatches || urlMatches.length === 0) {
3320
3675
  return { processedFiles: [], cleanedContent: content };
3321
3676
  }
3322
- const uniqueUrls = [...new Set(urlMatches)];
3677
+ const cleanedUrls = urlMatches.map((url) => url.replace(/["']+$/, ""));
3678
+ const uniqueUrls = [...new Set(cleanedUrls)];
3323
3679
  const encryptionKey = await getEncryptionKey(address);
3324
3680
  const processedFiles = [];
3325
3681
  let cleanedContent = content;
3682
+ const urlOccurrenceCounts = /* @__PURE__ */ new Map();
3683
+ uniqueUrls.forEach((url) => {
3684
+ const escapedUrl = url.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
3685
+ const htmlDoublePattern = new RegExp(
3686
+ `<img[^>]*src="${escapedUrl}"[^>]*>`,
3687
+ "gi"
3688
+ );
3689
+ const htmlSinglePattern = new RegExp(
3690
+ `<img[^>]*src='${escapedUrl}'[^>]*>`,
3691
+ "gi"
3692
+ );
3693
+ const markdownPattern = new RegExp(
3694
+ `!\\[[^\\]]*\\]\\([\\s]*${escapedUrl}[\\s]*\\)`,
3695
+ "g"
3696
+ );
3697
+ const rawPattern = new RegExp(escapedUrl, "g");
3698
+ const htmlDoubleMatches = content.match(htmlDoublePattern)?.length || 0;
3699
+ const htmlSingleMatches = content.match(htmlSinglePattern)?.length || 0;
3700
+ const markdownMatches = content.match(markdownPattern)?.length || 0;
3701
+ const rawMatches = (content.match(rawPattern)?.length || 0) - htmlDoubleMatches - htmlSingleMatches - markdownMatches;
3702
+ urlOccurrenceCounts.set(
3703
+ url,
3704
+ htmlDoubleMatches + htmlSingleMatches + markdownMatches + rawMatches
3705
+ );
3706
+ });
3326
3707
  const results = await Promise.allSettled(
3327
3708
  uniqueUrls.map(async (imageUrl) => {
3328
3709
  const controller = new AbortController();
@@ -3364,12 +3745,98 @@ function useChatStorage(options) {
3364
3745
  });
3365
3746
  const placeholder = createFilePlaceholder(fileId);
3366
3747
  const escapedUrl = imageUrl.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
3748
+ let replacementCount = 0;
3749
+ console.log(
3750
+ `[extractAndStoreEncryptedMCPImages] Replacing URL with placeholder:`,
3751
+ imageUrl,
3752
+ "->",
3753
+ placeholder
3754
+ );
3755
+ const htmlImgPatternDouble = new RegExp(
3756
+ `<img[^>]*src="${escapedUrl}"[^>]*>`,
3757
+ "gi"
3758
+ );
3759
+ const doubleMatches = cleanedContent.match(htmlImgPatternDouble);
3760
+ if (doubleMatches) {
3761
+ console.log(
3762
+ `[extractAndStoreEncryptedMCPImages] Replacing ${doubleMatches.length} HTML img tag(s) with double quotes:`,
3763
+ doubleMatches,
3764
+ "->",
3765
+ placeholder
3766
+ );
3767
+ replacementCount += doubleMatches.length;
3768
+ cleanedContent = cleanedContent.replace(
3769
+ htmlImgPatternDouble,
3770
+ placeholder
3771
+ );
3772
+ }
3773
+ const htmlImgPatternSingle = new RegExp(
3774
+ `<img[^>]*src='${escapedUrl}'[^>]*>`,
3775
+ "gi"
3776
+ );
3777
+ const singleMatches = cleanedContent.match(htmlImgPatternSingle);
3778
+ if (singleMatches) {
3779
+ console.log(
3780
+ `[extractAndStoreEncryptedMCPImages] Replacing ${singleMatches.length} HTML img tag(s) with single quotes:`,
3781
+ singleMatches,
3782
+ "->",
3783
+ placeholder
3784
+ );
3785
+ replacementCount += singleMatches.length;
3786
+ cleanedContent = cleanedContent.replace(
3787
+ htmlImgPatternSingle,
3788
+ placeholder
3789
+ );
3790
+ }
3367
3791
  const markdownImagePattern = new RegExp(
3368
3792
  `!\\[[^\\]]*\\]\\([\\s]*${escapedUrl}[\\s]*\\)`,
3369
3793
  "g"
3370
3794
  );
3371
- cleanedContent = cleanedContent.replace(markdownImagePattern, placeholder);
3372
- cleanedContent = cleanedContent.replace(new RegExp(escapedUrl, "g"), placeholder);
3795
+ const markdownMatches = cleanedContent.match(markdownImagePattern);
3796
+ if (markdownMatches) {
3797
+ console.log(
3798
+ `[extractAndStoreEncryptedMCPImages] Replacing ${markdownMatches.length} markdown image(s):`,
3799
+ markdownMatches,
3800
+ "->",
3801
+ placeholder
3802
+ );
3803
+ replacementCount += markdownMatches.length;
3804
+ cleanedContent = cleanedContent.replace(
3805
+ markdownImagePattern,
3806
+ placeholder
3807
+ );
3808
+ }
3809
+ const rawUrlPattern = new RegExp(escapedUrl, "g");
3810
+ const rawMatches = cleanedContent.match(rawUrlPattern);
3811
+ if (rawMatches) {
3812
+ console.log(
3813
+ `[extractAndStoreEncryptedMCPImages] Replacing ${rawMatches.length} raw URL(s):`,
3814
+ rawMatches,
3815
+ "->",
3816
+ placeholder
3817
+ );
3818
+ replacementCount += rawMatches.length;
3819
+ cleanedContent = cleanedContent.replace(rawUrlPattern, placeholder);
3820
+ }
3821
+ console.log(
3822
+ `[extractAndStoreEncryptedMCPImages] Total replacements made: ${replacementCount} for URL:`,
3823
+ imageUrl
3824
+ );
3825
+ const expectedCount = urlOccurrenceCounts.get(imageUrl) || 0;
3826
+ if (replacementCount < expectedCount) {
3827
+ console.warn(
3828
+ `[extractAndStoreEncryptedMCPImages] Not all instances of URL replaced. Expected ${expectedCount}, replaced ${replacementCount}:`,
3829
+ imageUrl
3830
+ );
3831
+ }
3832
+ const remainingPattern = new RegExp(escapedUrl, "g");
3833
+ const remainingMatches = cleanedContent.match(remainingPattern);
3834
+ if (remainingMatches && remainingMatches.length > 0) {
3835
+ console.warn(
3836
+ `[extractAndStoreEncryptedMCPImages] Found ${remainingMatches.length} remaining instance(s) of URL after replacement:`,
3837
+ imageUrl
3838
+ );
3839
+ }
3373
3840
  } else {
3374
3841
  console.error("[extractAndStoreEncryptedMCPImages] Failed:", result.reason);
3375
3842
  }