File size: 4,208 Bytes
f0743f4
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
const ARTIFACT_START = ':::artifact';
const ARTIFACT_END = ':::';

/**
 * Find all artifact boundaries in the message
 * @param {TMessage} message
 * @returns {Array<{start: number, end: number, source: 'content'|'text', partIndex?: number}>}
 */
const findAllArtifacts = (message) => {
  const artifacts = [];

  // Check content parts first
  if (message.content?.length) {
    message.content.forEach((part, partIndex) => {
      if (part.type === 'text' && typeof part.text === 'string') {
        let currentIndex = 0;
        let start = part.text.indexOf(ARTIFACT_START, currentIndex);

        while (start !== -1) {
          const end = part.text.indexOf(ARTIFACT_END, start + ARTIFACT_START.length);
          artifacts.push({
            start,
            end: end !== -1 ? end + ARTIFACT_END.length : part.text.length,
            source: 'content',
            partIndex,
            text: part.text,
          });

          currentIndex = end !== -1 ? end + ARTIFACT_END.length : part.text.length;
          start = part.text.indexOf(ARTIFACT_START, currentIndex);
        }
      }
    });
  }

  // Check message.text if no content parts
  if (!artifacts.length && message.text) {
    let currentIndex = 0;
    let start = message.text.indexOf(ARTIFACT_START, currentIndex);

    while (start !== -1) {
      const end = message.text.indexOf(ARTIFACT_END, start + ARTIFACT_START.length);
      artifacts.push({
        start,
        end: end !== -1 ? end + ARTIFACT_END.length : message.text.length,
        source: 'text',
        text: message.text,
      });

      currentIndex = end !== -1 ? end + ARTIFACT_END.length : message.text.length;
      start = message.text.indexOf(ARTIFACT_START, currentIndex);
    }
  }

  return artifacts;
};

const replaceArtifactContent = (originalText, artifact, original, updated) => {
  const artifactContent = artifact.text.substring(artifact.start, artifact.end);

  // Find boundaries between ARTIFACT_START and ARTIFACT_END
  const contentStart = artifactContent.indexOf('\n', artifactContent.indexOf(ARTIFACT_START)) + 1;
  let contentEnd = artifactContent.lastIndexOf(ARTIFACT_END);

  // Special case: if contentEnd is 0, it means the only ::: found is at the start of :::artifact
  // This indicates an incomplete artifact (no closing :::)
  // We need to check that it's exactly at position 0 (the beginning of artifactContent)
  if (contentEnd === 0 && artifactContent.indexOf(ARTIFACT_START) === 0) {
    contentEnd = artifactContent.length;
  }

  if (contentStart === -1 || contentEnd === -1) {
    return null;
  }

  // Check if there are code blocks
  const codeBlockStart = artifactContent.indexOf('```\n', contentStart);
  const codeBlockEnd = artifactContent.lastIndexOf('\n```', contentEnd);

  // Determine where to look for the original content
  let searchStart, searchEnd;
  if (codeBlockStart !== -1) {
    // Code block starts
    searchStart = codeBlockStart + 4; // after ```\n

    if (codeBlockEnd !== -1 && codeBlockEnd > codeBlockStart) {
      // Code block has proper ending
      searchEnd = codeBlockEnd;
    } else {
      // No closing backticks found or they're before the opening (shouldn't happen)
      // This might be an incomplete artifact - search to contentEnd
      searchEnd = contentEnd;
    }
  } else {
    // No code blocks at all
    searchStart = contentStart;
    searchEnd = contentEnd;
  }

  const innerContent = artifactContent.substring(searchStart, searchEnd);
  // Remove trailing newline from original for comparison
  const originalTrimmed = original.replace(/\n$/, '');
  const relativeIndex = innerContent.indexOf(originalTrimmed);

  if (relativeIndex === -1) {
    return null;
  }

  const absoluteIndex = artifact.start + searchStart + relativeIndex;
  const endText = originalText.substring(absoluteIndex + originalTrimmed.length);
  const hasTrailingNewline = endText.startsWith('\n');

  const updatedText =
    originalText.substring(0, absoluteIndex) + updated + (hasTrailingNewline ? '' : '\n') + endText;

  return updatedText.replace(/\n+(?=```\n:::)/g, '\n');
};

module.exports = {
  ARTIFACT_START,
  ARTIFACT_END,
  findAllArtifacts,
  replaceArtifactContent,
};