11import { AnalyticsEvent } from '@codebuff/common/constants/analytics-events'
22
3+ import {
4+ createStreamParserState ,
5+ parseStreamChunk ,
6+ } from './util/stream-xml-parser'
7+
8+ import type { StreamParserState } from './util/stream-xml-parser'
39import type { Model } from '@codebuff/common/old-constants'
410import type { TrackEventFn } from '@codebuff/common/types/contracts/analytics'
511import type { StreamChunk } from '@codebuff/common/types/contracts/llm'
@@ -31,6 +37,11 @@ export async function* processStreamWithTools(params: {
3137 agentName ?: string
3238 }
3339 trackEvent : TrackEventFn
40+ executeXmlToolCall : ( params : {
41+ toolCallId : string
42+ toolName : string
43+ input : Record < string , unknown >
44+ } ) => Promise < void >
3445} ) : AsyncGenerator < StreamChunk , string | null > {
3546 const {
3647 stream,
@@ -41,11 +52,15 @@ export async function* processStreamWithTools(params: {
4152 logger,
4253 loggerOptions,
4354 trackEvent,
55+ executeXmlToolCall,
4456 } = params
4557 let streamCompleted = false
4658 let buffer = ''
4759 let autocompleted = false
4860
61+ // State for parsing XML tool calls from text stream
62+ const xmlParserState : StreamParserState = createStreamParserState ( )
63+
4964 function processToolCallObject ( params : {
5065 toolName : string
5166 input : any
@@ -83,17 +98,48 @@ export async function* processStreamWithTools(params: {
8398 buffer = ''
8499 }
85100
86- function * processChunk (
101+ async function * processChunk (
87102 chunk : StreamChunk | undefined ,
88- ) : Generator < StreamChunk > {
103+ ) : AsyncGenerator < StreamChunk > {
89104 if ( chunk === undefined ) {
90105 flush ( )
91106 streamCompleted = true
92107 return
93108 }
94109
95110 if ( chunk . type === 'text' ) {
96- buffer += chunk . text
111+ // Parse XML tool calls from the text stream
112+ const { filteredText, toolCalls } = parseStreamChunk (
113+ chunk . text ,
114+ xmlParserState ,
115+ )
116+
117+ if ( filteredText ) {
118+ buffer += filteredText
119+ yield {
120+ type : 'text' ,
121+ text : filteredText ,
122+ }
123+ }
124+
125+ // Flush buffer before yielding tool calls so text event is sent first
126+ if ( toolCalls . length > 0 ) {
127+ flush ( )
128+ }
129+
130+ // Then process and yield any XML tool calls found
131+ for ( const toolCall of toolCalls ) {
132+ const toolCallId = `xml-${ crypto . randomUUID ( ) . slice ( 0 , 8 ) } `
133+
134+ // Execute the tool immediately if callback provided, pausing the stream
135+ // The callback handles emitting tool_call and tool_result events
136+ await executeXmlToolCall ( {
137+ toolCallId,
138+ toolName : toolCall . toolName ,
139+ input : toolCall . input ,
140+ } )
141+ }
142+ return
97143 } else {
98144 flush ( )
99145 }
0 commit comments