@@ -138,22 +138,6 @@ abstract class BaseChatAgent(prompt: String, model: String = AgentConfig.OpenAIM
138138 var cancelStreaming = false
139139 var streamingStarted = false
140140
141- // Define markdown markers
142- val boldMarker = " **"
143- val italicMarker = " *"
144- val codeMarker = " `"
145- val codeBlockStart = " ```"
146- val codeBlockEnd = " ```"
147- val bulletMarker = " - "
148- val headerMarker = " #"
149-
150- // Add state flags for markdown
151- var inBold = false
152- var inItalic = false
153- var inInlineCode = false
154- var inCodeBlock = false
155- var currentHeader = 0 // 0 means not in header, 1-6 represents h1-h6
156-
157141 val intSignal = new Signal (" INT" )
158142 val oldHandler = Signal .handle(intSignal, new SignalHandler {
159143 override def handle (sig : Signal ): Unit = {
@@ -177,7 +161,6 @@ abstract class BaseChatAgent(prompt: String, model: String = AgentConfig.OpenAIM
177161 if (delta.content().isPresent) {
178162 val content = delta.content().get()
179163 wordBuffer.append(content)
180- // We defer appending to currentMessageBuilder until content is finalized/printed
181164
182165 val toolStart = " <@TOOL>"
183166 val toolEnd = " </@TOOL>"
@@ -204,214 +187,44 @@ abstract class BaseChatAgent(prompt: String, model: String = AgentConfig.OpenAIM
204187 // End tag not yet in buffer, wait for more data
205188 // No partial printing for tool tags
206189 }
207- } else if (inCodeBlock) {
208- val endMarkerIndex = wordBuffer.indexOf(codeBlockEnd)
209- if (endMarkerIndex != - 1 ) {
210- // Found the end code block marker
211- val codeContent = wordBuffer.substring(0 , endMarkerIndex)
212- val fullCodeBlock = codeBlockStart + codeContent + codeBlockEnd
213- print(yellow(fullCodeBlock)) // Print the complete code block
214- currentMessageBuilder.append(fullCodeBlock) // Add to history
215- wordBuffer.delete(0 , endMarkerIndex + codeBlockEnd.length)
216- inCodeBlock = false
217- continueProcessingBuffer = true
218- } else {
219- // End marker not yet in buffer, wait for more data
220- }
221- } else if (inBold) {
222- val endMarkerIndex = wordBuffer.indexOf(boldMarker)
223- if (endMarkerIndex != - 1 ) {
224- // Found the end bold marker
225- val boldContent = wordBuffer.substring(0 , endMarkerIndex)
226- val fullBoldBlock = boldMarker + boldContent + boldMarker
227- print(consoleBold(fullBoldBlock)) // Print the complete bold block
228- currentMessageBuilder.append(fullBoldBlock) // Add to history
229- wordBuffer.delete(0 , endMarkerIndex + boldMarker.length)
230- inBold = false
231- continueProcessingBuffer = true
232- } else {
233- // End marker not yet in buffer, wait for more data
234- }
235- } else if (inItalic) {
236- val endMarkerIndex = wordBuffer.indexOf(italicMarker)
237- if (endMarkerIndex != - 1 ) {
238- // Found the end italic marker
239- val italicContent = wordBuffer.substring(0 , endMarkerIndex)
240- val fullItalicBlock = italicMarker + italicContent + italicMarker
241- print(green(fullItalicBlock)) // Print the complete italic block
242- currentMessageBuilder.append(fullItalicBlock) // Add to history
243- wordBuffer.delete(0 , endMarkerIndex + italicMarker.length)
244- inItalic = false
245- continueProcessingBuffer = true
246- } else {
247- // End marker not yet in buffer, wait for more data
248- }
249- } else if (inInlineCode) {
250- val endMarkerIndex = wordBuffer.indexOf(codeMarker)
251- if (endMarkerIndex != - 1 ) {
252- // Found the end code marker
253- val codeContent = wordBuffer.substring(0 , endMarkerIndex)
254- val fullCodeBlock = codeMarker + codeContent + codeMarker
255- print(yellow(fullCodeBlock)) // Print the complete code block
256- currentMessageBuilder.append(fullCodeBlock) // Add to history
257- wordBuffer.delete(0 , endMarkerIndex + codeMarker.length)
258- inInlineCode = false
259- continueProcessingBuffer = true
260- } else {
261- // End marker not yet in buffer, wait for more data
262- }
263- } else if (currentHeader > 0 ) {
264- // Headers end at the first newline
265- val endMarkerIndex = wordBuffer.indexOf(" \n " )
266- if (endMarkerIndex != - 1 ) {
267- val headerContent = wordBuffer.substring(0 , endMarkerIndex)
268- print(consoleBold(underline(headerContent)) + " \n " ) // Add newline after header
269- currentMessageBuilder.append(headerContent + " \n " ) // Add to history with newline
270- wordBuffer.delete(0 , endMarkerIndex + 1 ) // +1 to include the newline
271- currentHeader = 0
272- continueProcessingBuffer = true
273- } else {
274- // End of header not yet in buffer
275- }
276190 } else {
277- // Not in tool tag or markdown block: Look for start markers or process plain text
191+ // Not in tool tag: print plain text
278192 val toolStartIndex = wordBuffer.indexOf(toolStart)
279193 val toolResultStartIndex = wordBuffer.indexOf(toolResultStart)
280- val boldStartIndex = wordBuffer.indexOf(boldMarker)
281- val italicStartIndex = wordBuffer.indexOf(italicMarker)
282- val codeStartIndex = wordBuffer.indexOf(codeMarker)
283- val codeBlockStartIndex = wordBuffer.indexOf(codeBlockStart)
284-
285- // Check for bullet points
286- val bulletStartIndex = wordBuffer.indexOf(bulletMarker)
287- val isBulletStart = bulletStartIndex != - 1 &&
288- (bulletStartIndex == 0 || wordBuffer.charAt(bulletStartIndex - 1 ) == '\n ' )
289-
290- // Check for headers
291- val headerStartIndex = wordBuffer.indexOf(headerMarker)
292- val isHeaderStart = headerStartIndex != - 1 &&
293- (headerStartIndex == 0 || wordBuffer.charAt(headerStartIndex - 1 ) == '\n ' )
294-
295- // Find the earliest marker index
296- val markers = List (
297- (toolStartIndex, toolStart, toolEnd, false ), // (index, startMarker, endMarker, isMarkdown)
298- (toolResultStartIndex, toolResultStart, toolResultEnd, false ),
299- (boldStartIndex, boldMarker, boldMarker, true ),
300- (italicStartIndex, italicMarker, italicMarker, true ),
301- (codeStartIndex, codeMarker, codeMarker, true ),
302- (codeBlockStartIndex, codeBlockStart, codeBlockEnd, true ),
303- (if (isBulletStart) bulletStartIndex else - 1 , bulletMarker, " \n " , true ),
304- (if (isHeaderStart) headerStartIndex else - 1 , headerMarker, " \n " , true )
305- ).filter(_._1 != - 1 ).sortBy(_._1) // Keep only found markers, sort by index
306-
307- if (markers.nonEmpty) {
308- val (startIndex, startMarker, endMarker, isMarkdownMarker) = markers.head
309-
310- // Process plain text before the marker
311- if (startIndex > 0 ) {
312- val beforeMarker = wordBuffer.substring(0 , startIndex)
313- val (words, remaining) = processWords(beforeMarker)
314- if (words.nonEmpty) {
315- val processedText = words.map { case (word, ws) =>
316- print(blue(word)); print(ws); word + ws // Print and collect text
317- }.mkString
318- currentMessageBuilder.append(processedText) // Add printed text to history
319- }
320- // Handle any remaining partial word - print it as is for now
321- if (remaining.nonEmpty) {
322- print(blue(remaining))
323- currentMessageBuilder.append(remaining)
324- }
325- wordBuffer.delete(0 , startIndex) // Consume the processed plain text
194+ val nextTagIndex =
195+ if (toolStartIndex == - 1 && toolResultStartIndex == - 1 ) - 1
196+ else if (toolStartIndex == - 1 ) toolResultStartIndex
197+ else if (toolResultStartIndex == - 1 ) toolStartIndex
198+ else Math .min(toolStartIndex, toolResultStartIndex)
199+
200+ if (nextTagIndex != - 1 ) {
201+ // Print up to the next tag
202+ val beforeTag = wordBuffer.substring(0 , nextTagIndex)
203+ print(blue(beforeTag))
204+ currentMessageBuilder.append(beforeTag)
205+ wordBuffer.delete(0 , nextTagIndex)
206+ continueProcessingBuffer = true
207+ // Now handle the tag
208+ if (wordBuffer.startsWith(toolStart)) {
209+ if (AppConfig .isDebugMode) print(red(toolStart)) else print(" " )
210+ currentMessageBuilder.append(toolStart)
211+ wordBuffer.delete(0 , toolStart.length)
212+ isInToolTag = true
213+ currentToolTagEndMarker = Some (toolEnd)
326214 continueProcessingBuffer = true
327- }
328-
329- // Handle the marker itself
330- if (wordBuffer.startsWith(startMarker)) {
331- if (isMarkdownMarker) {
332- // Handle different markdown element starts
333- if (startMarker == boldMarker) {
334- // Skip processing if it's actually part of code block start
335- if (! wordBuffer.startsWith(codeBlockStart)) {
336- wordBuffer.delete(0 , boldMarker.length)
337- inBold = true
338- continueProcessingBuffer = true
339- }
340- } else if (startMarker == italicMarker) {
341- // Make sure it's not part of bold marker or already in bold
342- if (! wordBuffer.startsWith(boldMarker) && ! inBold) {
343- wordBuffer.delete(0 , italicMarker.length)
344- inItalic = true
345- continueProcessingBuffer = true
346- }
347- } else if (startMarker == codeMarker) {
348- // Make sure it's not part of code block marker
349- if (! wordBuffer.startsWith(codeBlockStart)) {
350- wordBuffer.delete(0 , codeMarker.length)
351- inInlineCode = true
352- continueProcessingBuffer = true
353- }
354- } else if (startMarker == codeBlockStart) {
355- wordBuffer.delete(0 , codeBlockStart.length)
356- inCodeBlock = true
357- continueProcessingBuffer = true
358- } else if (startMarker == bulletMarker) {
359- // Handle bullet points
360- val lineEndIndex = wordBuffer.indexOf(" \n " )
361- if (lineEndIndex != - 1 ) {
362- val bulletLine = wordBuffer.substring(0 , lineEndIndex)
363- print(green(bulletLine + " \n " ))
364- currentMessageBuilder.append(bulletLine + " \n " )
365- wordBuffer.delete(0 , lineEndIndex + 1 )
366- } else {
367- // End of bullet not found yet, continue with other markers for now
368- }
369- continueProcessingBuffer = true
370- } else if (startMarker == headerMarker) {
371- // Handle headers - count number of # symbols
372- var headerLevel = 0
373- var i = 0
374- while (i < wordBuffer.length && wordBuffer.charAt(i) == '#' ) {
375- headerLevel += 1
376- i += 1
377- }
378-
379- if (headerLevel > 0 && i < wordBuffer.length && wordBuffer.charAt(i) == ' ' ) {
380- // Valid header format - print in special format
381- currentHeader = headerLevel
382- print(consoleBold(underline(wordBuffer.substring(0 , i + 1 ))))
383- currentMessageBuilder.append(wordBuffer.substring(0 , i + 1 ))
384- wordBuffer.delete(0 , i + 1 )
385- continueProcessingBuffer = true
386- } else {
387- // Not a proper header format, treat as normal text
388- }
389- }
390- } else {
391- // Start of a tool tag block
392- if (AppConfig .isDebugMode) print(red(startMarker)) else print(" " ) // Don't print tool tag markers
393- currentMessageBuilder.append(startMarker) // Add start tag to history
394- wordBuffer.delete(0 , startMarker.length)
395- isInToolTag = true
396- currentToolTagEndMarker = Some (endMarker)
397- continueProcessingBuffer = true
398- }
399- }
400- // If wordBuffer doesn't start with marker after deleting prefix, loop again
401-
402- } else {
403- // No markers found, process buffer as plain text
404- val (words, remaining) = processWords(wordBuffer.toString())
405- if (words.nonEmpty) {
406- val processedText = words.map{ case (word, ws) =>
407- print(blue(word)); print(ws); word + ws // Print and collect text
408- }.mkString
409- currentMessageBuilder.append(processedText) // Add printed text to history
410- val processedLength = wordBuffer.length() - remaining.length()
411- wordBuffer.delete(0 , processedLength)
215+ } else if (wordBuffer.startsWith(toolResultStart)) {
216+ if (AppConfig .isDebugMode) print(red(toolResultStart)) else print(" " )
217+ currentMessageBuilder.append(toolResultStart)
218+ wordBuffer.delete(0 , toolResultStart.length)
219+ isInToolTag = true
220+ currentToolTagEndMarker = Some (toolResultEnd)
412221 continueProcessingBuffer = true
413222 }
414- // Keep 'remaining' in buffer for next iteration or chunk
223+ } else {
224+ // No tags, print everything
225+ print(blue(wordBuffer.toString()))
226+ currentMessageBuilder.append(wordBuffer.toString())
227+ wordBuffer.clear()
415228 }
416229 }
417230 } // End of inner buffer processing loop
@@ -420,15 +233,13 @@ abstract class BaseChatAgent(prompt: String, model: String = AgentConfig.OpenAIM
420233
421234 // After the loop, process any remaining content in the buffer
422235 if (wordBuffer.nonEmpty) {
423- // If streaming was cancelled or ended mid-tag/markdown, print remaining plainly
424- if (isInToolTag || inBold || inItalic || inInlineCode || inCodeBlock || currentHeader > 0 ) {
425- if (AppConfig .isDebugMode) print(red(wordBuffer.toString())) else print(blue(wordBuffer.toString()))
426- } else {
427- // Print remaining plain text
428- print(blue(wordBuffer.toString()))
429- }
430- currentMessageBuilder.append(wordBuffer.toString()) // Append whatever is left to history
431- wordBuffer.clear()
236+ if (isInToolTag) {
237+ if (AppConfig .isDebugMode) print(red(wordBuffer.toString())) else print(blue(wordBuffer.toString()))
238+ } else {
239+ print(blue(wordBuffer.toString()))
240+ }
241+ currentMessageBuilder.append(wordBuffer.toString()) // Append whatever is left to history
242+ wordBuffer.clear()
432243 }
433244
434245 if (cancelStreaming) {
@@ -470,33 +281,6 @@ abstract class BaseChatAgent(prompt: String, model: String = AgentConfig.OpenAIM
470281 }
471282 }
472283
473- // Helper function to process words and whitespace
474- private def processWords (text : String ): (ListBuffer [(String , String )], String ) = {
475- val words = ListBuffer [(String , String )]()
476- var remainingText = text
477- var continueProcessing = true
478-
479- while (continueProcessing) {
480- val whitespaceIndex = remainingText.indexWhere(_.isWhitespace)
481- if (whitespaceIndex != - 1 ) {
482- val word = remainingText.substring(0 , whitespaceIndex)
483- val whitespace = remainingText.substring(whitespaceIndex).takeWhile(_.isWhitespace)
484- if (word.nonEmpty) {
485- words += ((word, whitespace))
486- } else {
487- // Handle leading whitespace? For now, just consume it with the next word or as trailing.
488- // If printing just whitespace: print(whitespace)
489- }
490- remainingText = remainingText.substring(whitespaceIndex + whitespace.length)
491- if (remainingText.isEmpty) continueProcessing = false
492- } else {
493- // No more whitespace, the rest is a partial word or empty
494- continueProcessing = false
495- }
496- }
497- (words, remainingText) // Return processed words and any remaining partial word
498- }
499-
500284 private def handleToolCall (toolCall : ToolCallDescription ): Unit = {
501285 val toolResult = toolExecution(toolCall)
502286
0 commit comments