const ARTIFACT_TAG_OPEN = ' void; type ArtifactCloseCallback = (messageId: string) => void; type ActionCallback = (messageId: string, action: BoltActionData) => void; type ActionType = 'file' | 'shell'; export interface BoltActionData { type?: ActionType; path?: string; content: string; } interface Callbacks { onArtifactOpen?: ArtifactOpenCallback; onArtifactClose?: ArtifactCloseCallback; onAction?: ActionCallback; } type ElementFactory = () => string; interface StreamingMessageParserOptions { callbacks?: Callbacks; artifactElement?: string | ElementFactory; } export class StreamingMessageParser { #lastPositions = new Map(); #insideArtifact = false; #insideAction = false; #currentAction: BoltActionData = { content: '' }; constructor(private _options: StreamingMessageParserOptions = {}) {} parse(id: string, input: string) { let output = ''; let i = this.#lastPositions.get(id) ?? 0; let earlyBreak = false; while (i < input.length) { if (this.#insideArtifact) { if (this.#insideAction) { const closeIndex = input.indexOf(ARTIFACT_ACTION_TAG_CLOSE, i); if (closeIndex !== -1) { this.#currentAction.content += input.slice(i, closeIndex); let content = this.#currentAction.content.trim(); if (this.#currentAction.type === 'file') { content += '\n'; } this.#currentAction.content = content; this._options.callbacks?.onAction?.(id, this.#currentAction); this.#insideAction = false; this.#currentAction = { content: '' }; i = closeIndex + ARTIFACT_ACTION_TAG_CLOSE.length; } else { break; } } else { const actionOpenIndex = input.indexOf(ARTIFACT_ACTION_TAG_OPEN, i); const artifactCloseIndex = input.indexOf(ARTIFACT_TAG_CLOSE, i); if (actionOpenIndex !== -1 && (artifactCloseIndex === -1 || actionOpenIndex < artifactCloseIndex)) { const actionEndIndex = input.indexOf('>', actionOpenIndex); if (actionEndIndex !== -1) { const actionTag = input.slice(actionOpenIndex, actionEndIndex + 1); this.#currentAction.type = this.#extractAttribute(actionTag, 'type') as ActionType; this.#currentAction.path = this.#extractAttribute(actionTag, 'path'); this.#insideAction = true; i = actionEndIndex + 1; } else { break; } } else if (artifactCloseIndex !== -1) { this.#insideArtifact = false; this._options.callbacks?.onArtifactClose?.(id); i = artifactCloseIndex + ARTIFACT_TAG_CLOSE.length; } else { break; } } } else if (input[i] === '<' && input[i + 1] !== '/') { let j = i; let potentialTag = ''; while (j < input.length && potentialTag.length < ARTIFACT_TAG_OPEN.length) { potentialTag += input[j]; if (potentialTag === ARTIFACT_TAG_OPEN) { const nextChar = input[j + 1]; if (nextChar && nextChar !== '>' && nextChar !== ' ') { output += input.slice(i, j + 1); i = j + 1; break; } const openTagEnd = input.indexOf('>', j); if (openTagEnd !== -1) { const artifactTag = input.slice(i, openTagEnd + 1); const artifactTitle = this.#extractAttribute(artifactTag, 'title') as string; this.#insideArtifact = true; this._options.callbacks?.onArtifactOpen?.(id, { title: artifactTitle }); output += this._options.artifactElement ?? `
`; i = openTagEnd + 1; } else { earlyBreak = true; } break; } else if (!ARTIFACT_TAG_OPEN.startsWith(potentialTag)) { output += input.slice(i, j + 1); i = j + 1; break; } j++; } if (j === input.length && ARTIFACT_TAG_OPEN.startsWith(potentialTag)) { break; } } else { output += input[i]; i++; } if (earlyBreak) { break; } } this.#lastPositions.set(id, i); return output; } reset() { this.#lastPositions.clear(); this.#insideArtifact = false; this.#insideAction = false; this.#currentAction = { content: '' }; } #extractAttribute(tag: string, attributeName: string): string | undefined { const match = tag.match(new RegExp(`${attributeName}="([^"]*)"`, 'i')); return match ? match[1] : undefined; } }