@@ -17,6 +17,9 @@ export class AssistantMessageParser {
1717 private readonly MAX_ACCUMULATOR_SIZE = 1024 * 1024 // 1MB limit
1818 private readonly MAX_PARAM_LENGTH = 1024 * 100 // 100KB per parameter limit
1919 private accumulator = ""
20+ private inCodeBlock = false
21+ private inInlineCode = false
22+ private codeBlockDelimiterCount = 0
2023
2124 /**
2225 * Initialize a new AssistantMessageParser instance.
@@ -37,6 +40,9 @@ export class AssistantMessageParser {
3740 this . currentParamName = undefined
3841 this . currentParamValueStartIndex = 0
3942 this . accumulator = ""
43+ this . inCodeBlock = false
44+ this . inInlineCode = false
45+ this . codeBlockDelimiterCount = 0
4046 }
4147
4248 /**
@@ -63,6 +69,41 @@ export class AssistantMessageParser {
6369 this . accumulator += char
6470 const currentPosition = accumulatorStartLength + i
6571
72+ // Track code blocks and inline code
73+ if ( char === "`" ) {
74+ this . codeBlockDelimiterCount ++
75+ if ( this . codeBlockDelimiterCount === 3 ) {
76+ this . inCodeBlock = ! this . inCodeBlock
77+ this . codeBlockDelimiterCount = 0
78+ this . inInlineCode = false // Code blocks take precedence
79+ }
80+ } else {
81+ // If we had one backtick and now a different char, toggle inline code
82+ if ( this . codeBlockDelimiterCount === 1 && ! this . inCodeBlock ) {
83+ this . inInlineCode = ! this . inInlineCode
84+ }
85+ this . codeBlockDelimiterCount = 0
86+ }
87+
88+ // Skip tool parsing if we're inside code blocks or inline code
89+ if ( this . inCodeBlock || this . inInlineCode ) {
90+ // Continue accumulating text content
91+ if ( this . currentTextContent === undefined && ! this . currentToolUse ) {
92+ this . currentTextContentStartIndex = currentPosition
93+ this . currentTextContent = {
94+ type : "text" ,
95+ content : this . accumulator . slice ( this . currentTextContentStartIndex ) . trim ( ) ,
96+ partial : true ,
97+ }
98+ // Add the new text content to contentBlocks immediately
99+ this . contentBlocks . push ( this . currentTextContent )
100+ } else if ( this . currentTextContent ) {
101+ // Update the existing text content
102+ this . currentTextContent . content = this . accumulator . slice ( this . currentTextContentStartIndex ) . trim ( )
103+ }
104+ continue
105+ }
106+
66107 // There should not be a param without a tool use.
67108 if ( this . currentToolUse && this . currentParamName ) {
68109 const currentParamValue = this . accumulator . slice ( this . currentParamValueStartIndex )
@@ -159,47 +200,50 @@ export class AssistantMessageParser {
159200
160201 for ( const toolUseOpeningTag of possibleToolUseOpeningTags ) {
161202 if ( this . accumulator . endsWith ( toolUseOpeningTag ) ) {
162- // Extract and validate the tool name
163- const extractedToolName = toolUseOpeningTag . slice ( 1 , - 1 )
203+ // Only process tool tags if we're not in code blocks
204+ if ( ! this . inCodeBlock && ! this . inInlineCode ) {
205+ // Extract and validate the tool name
206+ const extractedToolName = toolUseOpeningTag . slice ( 1 , - 1 )
164207
165- // Check if the extracted tool name is valid
166- if ( ! toolNames . includes ( extractedToolName as ToolName ) ) {
167- // Invalid tool name, treat as plain text and continue
168- continue
169- }
208+ // Check if the extracted tool name is valid
209+ if ( ! toolNames . includes ( extractedToolName as ToolName ) ) {
210+ // Invalid tool name, treat as plain text and continue
211+ continue
212+ }
170213
171- // Start of a new tool use.
172- this . currentToolUse = {
173- type : "tool_use" ,
174- name : extractedToolName as ToolName ,
175- params : { } ,
176- partial : true ,
177- }
214+ // Start of a new tool use.
215+ this . currentToolUse = {
216+ type : "tool_use" ,
217+ name : extractedToolName as ToolName ,
218+ params : { } ,
219+ partial : true ,
220+ }
178221
179- this . currentToolUseStartIndex = this . accumulator . length
222+ this . currentToolUseStartIndex = this . accumulator . length
180223
181- // This also indicates the end of the current text content.
182- if ( this . currentTextContent ) {
183- this . currentTextContent . partial = false
224+ // This also indicates the end of the current text content.
225+ if ( this . currentTextContent ) {
226+ this . currentTextContent . partial = false
184227
185- // Remove the partially accumulated tool use tag from the
186- // end of text (<tool).
187- this . currentTextContent . content = this . currentTextContent . content
188- . slice ( 0 , - toolUseOpeningTag . slice ( 0 , - 1 ) . length )
189- . trim ( )
228+ // Remove the partially accumulated tool use tag from the
229+ // end of text (<tool).
230+ this . currentTextContent . content = this . currentTextContent . content
231+ . slice ( 0 , - toolUseOpeningTag . slice ( 0 , - 1 ) . length )
232+ . trim ( )
190233
191- // No need to push, currentTextContent is already in contentBlocks
192- this . currentTextContent = undefined
193- }
234+ // No need to push, currentTextContent is already in contentBlocks
235+ this . currentTextContent = undefined
236+ }
194237
195- // Immediately push new tool_use block as partial
196- let idx = this . contentBlocks . findIndex ( ( block ) => block === this . currentToolUse )
197- if ( idx === - 1 ) {
198- this . contentBlocks . push ( this . currentToolUse )
199- }
238+ // Immediately push new tool_use block as partial
239+ let idx = this . contentBlocks . findIndex ( ( block ) => block === this . currentToolUse )
240+ if ( idx === - 1 ) {
241+ this . contentBlocks . push ( this . currentToolUse )
242+ }
200243
201- didStartToolUse = true
202- break
244+ didStartToolUse = true
245+ break
246+ }
203247 }
204248 }
205249
0 commit comments