@@ -46,6 +46,14 @@ export function parseAssistantMessageV2(assistantMessage: string): AssistantMess
4646 let currentParamValueStart = 0 // Index *after* the opening tag of the current param.
4747 let currentParamName : ToolParamName | undefined = undefined
4848
49+ // Track whether we are inside markdown code blocks or inline code to avoid treating textual mentions
50+ // of tool tags (e.g. <read_file>) as actual tool invocations.
51+ let insideCodeBlock = false // ``` fenced code block
52+ let insideInlineCode = false // `inline code`
53+
54+ // Helper to decide if we should parse for tool-related tags at the current position
55+ const shouldParseToolTags = ( ) => ! insideCodeBlock && ! insideInlineCode
56+
4957 // Precompute tags for faster lookups.
5058 const toolUseOpenTags = new Map < string , ToolName > ( )
5159 const toolParamOpenTags = new Map < string , ToolParamName > ( )
@@ -63,16 +71,39 @@ export function parseAssistantMessageV2(assistantMessage: string): AssistantMess
6371 for ( let i = 0 ; i < len ; i ++ ) {
6472 const currentCharIndex = i
6573
74+ // Detect fenced code block (```).
75+ if ( ! insideInlineCode && i + 2 < len && assistantMessage . slice ( i , i + 3 ) === "```" ) {
76+ insideCodeBlock = ! insideCodeBlock
77+ i += 2 // Skip the two extra backticks.
78+ continue
79+ }
80+
81+ // Detect inline code (`) when not inside a fenced code block and not
82+ // part of triple backticks.
83+ if ( ! insideCodeBlock && assistantMessage [ i ] === "`" ) {
84+ insideInlineCode = ! insideInlineCode
85+ }
86+
87+ // If we are in any kind of code context, treat everything as plain text.
88+ if ( ! shouldParseToolTags ( ) ) {
89+ // If we're not already tracking text content, start now.
90+ if ( ! currentTextContent ) {
91+ currentTextContentStart = currentCharIndex
92+ currentTextContent = { type : "text" , content : "" , partial : true }
93+ }
94+
95+ continue
96+ }
97+
6698 // Parsing a tool parameter
6799 if ( currentToolUse && currentParamName ) {
68100 const closeTag = `</${ currentParamName } >`
69- // Check if the string *ending* at index `i` matches the closing tag
101+
102+ // Check if the string *ending* at index `i` matches the closing tag.
70103 if (
71104 currentCharIndex >= closeTag . length - 1 &&
72- assistantMessage . startsWith (
73- closeTag ,
74- currentCharIndex - closeTag . length + 1 , // Start checking from potential start of tag.
75- )
105+ // Start checking from potential start of tag.
106+ assistantMessage . startsWith ( closeTag , currentCharIndex - closeTag . length + 1 )
76107 ) {
77108 // Found the closing tag for the parameter.
78109 const value = assistantMessage
@@ -175,6 +206,23 @@ export function parseAssistantMessageV2(assistantMessage: string): AssistantMess
175206 currentCharIndex >= tag . length - 1 &&
176207 assistantMessage . startsWith ( tag , currentCharIndex - tag . length + 1 )
177208 ) {
209+ // Check that this is likely an actual tool invocation and not
210+ // an inline textual reference.
211+ // We consider it an invocation only if the next non-whitespace
212+ // character is a newline (\n or \r) or an opening angle bracket
213+ // '<' (which would start the first parameter tag).
214+ let j = currentCharIndex + 1 // Position after the closing '>' of the opening tag.
215+
216+ while ( j < len && assistantMessage [ j ] === " " ) {
217+ j ++
218+ }
219+
220+ const nextChar = assistantMessage [ j ] ?? ""
221+
222+ if ( nextChar && nextChar !== "<" && nextChar !== "\n" && nextChar !== "\r" ) {
223+ // Treat as plain text, not a tool invocation.
224+ continue
225+ }
178226 // End current text block if one was active.
179227 if ( currentTextContent ) {
180228 currentTextContent . content = assistantMessage
@@ -201,22 +249,13 @@ export function parseAssistantMessageV2(assistantMessage: string): AssistantMess
201249 . trim ( )
202250
203251 if ( potentialText . length > 0 ) {
204- contentBlocks . push ( {
205- type : "text" ,
206- content : potentialText ,
207- partial : false ,
208- } )
252+ contentBlocks . push ( { type : "text" , content : potentialText , partial : false } )
209253 }
210254 }
211255
212256 // Start the new tool use.
213- currentToolUse = {
214- type : "tool_use" ,
215- name : toolName ,
216- params : { } ,
217- partial : true , // Assume partial until closing tag is found.
218- }
219-
257+ // Assume partial until closing tag is found.
258+ currentToolUse = { type : "tool_use" , name : toolName , params : { } , partial : true }
220259 currentToolUseStart = currentCharIndex + 1 // Tool content starts after the opening tag.
221260 startedNewTool = true
222261
0 commit comments