| 57 | constructor(private _options: StreamingMessageParserOptions = {}) {} |
| 58 | |
| 59 | parse(messageId: string, input: string) { |
| 60 | let state = this.#messages.get(messageId); |
| 61 | |
| 62 | if (!state) { |
| 63 | state = { |
| 64 | position: 0, |
| 65 | insideAction: false, |
| 66 | insideArtifact: false, |
| 67 | currentAction: { content: '' }, |
| 68 | actionId: 0, |
| 69 | }; |
| 70 | |
| 71 | this.#messages.set(messageId, state); |
| 72 | } |
| 73 | |
| 74 | let output = ''; |
| 75 | let i = state.position; |
| 76 | let earlyBreak = false; |
| 77 | |
| 78 | while (i < input.length) { |
| 79 | if (state.insideArtifact) { |
| 80 | const currentArtifact = state.currentArtifact; |
| 81 | |
| 82 | if (currentArtifact === undefined) { |
| 83 | unreachable('Artifact not initialized'); |
| 84 | } |
| 85 | |
| 86 | if (state.insideAction) { |
| 87 | const closeIndex = input.indexOf(ARTIFACT_ACTION_TAG_CLOSE, i); |
| 88 | |
| 89 | const currentAction = state.currentAction; |
| 90 | |
| 91 | if (closeIndex !== -1) { |
| 92 | currentAction.content += input.slice(i, closeIndex); |
| 93 | |
| 94 | let content = currentAction.content.trim(); |
| 95 | |
| 96 | if ('type' in currentAction && currentAction.type === 'file') { |
| 97 | content += '\n'; |
| 98 | } |
| 99 | |
| 100 | currentAction.content = content; |
| 101 | |
| 102 | this._options.callbacks?.onActionClose?.({ |
| 103 | artifactId: currentArtifact.id, |
| 104 | messageId, |
| 105 | |
| 106 | /** |
| 107 | * We decrement the id because it's been incremented already |
| 108 | * when `onActionOpen` was emitted to make sure the ids are |
| 109 | * the same. |
| 110 | */ |
| 111 | actionId: String(state.actionId - 1), |
| 112 | |
| 113 | action: currentAction as BoltAction, |
| 114 | }); |
| 115 | |
| 116 | state.insideAction = false; |