@@ -44,14 +44,20 @@ export const xAIProvider: ProviderConfig = {
4444 throw new Error ( 'API key is required for xAI' )
4545 }
4646
47- // Initialize OpenAI client for xAI
4847 const xai = new OpenAI ( {
4948 apiKey : request . apiKey ,
5049 baseURL : 'https://api.x.ai/v1' ,
5150 } )
5251
53- // Prepare messages
54- const allMessages = [ ]
52+ logger . info ( 'XAI Provider - Initial request configuration:' , {
53+ hasTools : ! ! request . tools ?. length ,
54+ toolCount : request . tools ?. length || 0 ,
55+ hasResponseFormat : ! ! request . responseFormat ,
56+ model : request . model || 'grok-3-latest' ,
57+ streaming : ! ! request . stream ,
58+ } )
59+
60+ const allMessages : any [ ] = [ ]
5561
5662 if ( request . systemPrompt ) {
5763 allMessages . push ( {
@@ -83,77 +89,71 @@ export const xAIProvider: ProviderConfig = {
8389 } ) )
8490 : undefined
8591
86- // Build the request payload
87- const payload : any = {
92+ // Log tools and response format conflict detection
93+ if ( tools ?. length && request . responseFormat ) {
94+ logger . warn (
95+ 'XAI Provider - Detected both tools and response format. Using tools first, then response format for final response.'
96+ )
97+ }
98+
99+ // Build the base request payload
100+ const basePayload : any = {
88101 model : request . model || 'grok-3-latest' ,
89102 messages : allMessages ,
90103 }
91104
92- if ( request . temperature !== undefined ) payload . temperature = request . temperature
93- if ( request . maxTokens !== undefined ) payload . max_tokens = request . maxTokens
105+ if ( request . temperature !== undefined ) basePayload . temperature = request . temperature
106+ if ( request . maxTokens !== undefined ) basePayload . max_tokens = request . maxTokens
94107
95- if ( request . responseFormat ) {
96- payload . response_format = {
97- type : 'json_schema' ,
98- json_schema : {
99- name : request . responseFormat . name || 'structured_response' ,
100- schema : request . responseFormat . schema || request . responseFormat ,
101- strict : request . responseFormat . strict !== false ,
102- } ,
108+ // Function to create response format configuration
109+ const createResponseFormatPayload = ( messages : any [ ] = allMessages ) => {
110+ const payload = {
111+ ...basePayload ,
112+ messages,
103113 }
104114
105- if ( allMessages . length > 0 && allMessages [ 0 ] . role === 'system' ) {
106- allMessages [ 0 ] . content = `${ allMessages [ 0 ] . content } \n\nYou MUST respond with a valid JSON object. DO NOT include any other text, explanations, or markdown formatting in your response - ONLY the JSON object.`
107- } else {
108- allMessages . unshift ( {
109- role : 'system' ,
110- content :
111- 'You MUST respond with a valid JSON object. DO NOT include any other text, explanations, or markdown formatting in your response - ONLY the JSON object.' ,
112- } )
115+ if ( request . responseFormat ) {
116+ payload . response_format = {
117+ type : 'json_schema' ,
118+ json_schema : {
119+ name : request . responseFormat . name || 'structured_response' ,
120+ schema : request . responseFormat . schema || request . responseFormat ,
121+ strict : request . responseFormat . strict !== false ,
122+ } ,
123+ }
113124 }
125+
126+ return payload
114127 }
115128
116129 // Handle tools and tool usage control
117130 let preparedTools : ReturnType < typeof prepareToolsWithUsageControl > | null = null
118131
119132 if ( tools ?. length ) {
120133 preparedTools = prepareToolsWithUsageControl ( tools , request . tools , logger , 'xai' )
121- const { tools : filteredTools , toolChoice } = preparedTools
122-
123- if ( filteredTools ?. length && toolChoice ) {
124- payload . tools = filteredTools
125- payload . tool_choice = toolChoice
126-
127- logger . info ( 'XAI request configuration:' , {
128- toolCount : filteredTools . length ,
129- toolChoice :
130- typeof toolChoice === 'string'
131- ? toolChoice
132- : toolChoice . type === 'function'
133- ? `force:${ toolChoice . function . name } `
134- : toolChoice . type === 'tool'
135- ? `force:${ toolChoice . name } `
136- : toolChoice . type === 'any'
137- ? `force:${ toolChoice . any ?. name || 'unknown' } `
138- : 'unknown' ,
139- model : request . model || 'grok-3-latest' ,
140- } )
141- }
142134 }
143135
144136 // EARLY STREAMING: if caller requested streaming and there are no tools to execute,
145- // we can directly stream the completion.
137+ // we can directly stream the completion with response format if needed
146138 if ( request . stream && ( ! tools || tools . length === 0 ) ) {
147- logger . info ( 'Using streaming response for XAI request (no tools)' )
139+ logger . info ( 'XAI Provider - Using direct streaming (no tools)' )
148140
149141 // Start execution timer for the entire provider execution
150142 const providerStartTime = Date . now ( )
151143 const providerStartTimeISO = new Date ( providerStartTime ) . toISOString ( )
152144
153- const streamResponse = await xai . chat . completions . create ( {
154- ...payload ,
155- stream : true ,
156- } )
145+ // Use response format payload if needed, otherwise use base payload
146+ const streamingPayload = request . responseFormat
147+ ? createResponseFormatPayload ( )
148+ : { ...basePayload , stream : true }
149+
150+ if ( ! request . responseFormat ) {
151+ streamingPayload . stream = true
152+ } else {
153+ streamingPayload . stream = true
154+ }
155+
156+ const streamResponse = await xai . chat . completions . create ( streamingPayload )
157157
158158 // Start collecting token usage
159159 const tokenUsage = {
@@ -217,14 +217,29 @@ export const xAIProvider: ProviderConfig = {
217217 // Make the initial API request
218218 const initialCallTime = Date . now ( )
219219
220+ // For the initial request with tools, we NEVER include response_format
221+ // This is the key fix: tools and response_format cannot be used together with xAI
222+ const initialPayload = { ...basePayload }
223+
220224 // Track the original tool_choice for forced tool tracking
221- const originalToolChoice = payload . tool_choice
225+ let originalToolChoice : any
222226
223227 // Track forced tools and their usage
224228 const forcedTools = preparedTools ?. forcedTools || [ ]
225229 let usedForcedTools : string [ ] = [ ]
226230
227- let currentResponse = await xai . chat . completions . create ( payload )
231+ if ( preparedTools ?. tools ?. length && preparedTools . toolChoice ) {
232+ const { tools : filteredTools , toolChoice } = preparedTools
233+ initialPayload . tools = filteredTools
234+ initialPayload . tool_choice = toolChoice
235+ originalToolChoice = toolChoice
236+ } else if ( request . responseFormat ) {
237+ // Only add response format if there are no tools
238+ const responseFormatPayload = createResponseFormatPayload ( )
239+ Object . assign ( initialPayload , responseFormatPayload )
240+ }
241+
242+ let currentResponse = await xai . chat . completions . create ( initialPayload )
228243 const firstResponseTime = Date . now ( ) - initialCallTime
229244
230245 let content = currentResponse . choices [ 0 ] ?. message ?. content || ''
@@ -278,7 +293,9 @@ export const xAIProvider: ProviderConfig = {
278293 }
279294
280295 // Check if a forced tool was used in the first response
281- checkForForcedToolUsage ( currentResponse , originalToolChoice )
296+ if ( originalToolChoice ) {
297+ checkForForcedToolUsage ( currentResponse , originalToolChoice )
298+ }
282299
283300 try {
284301 while ( iterationCount < MAX_ITERATIONS ) {
@@ -297,7 +314,10 @@ export const xAIProvider: ProviderConfig = {
297314 const toolArgs = JSON . parse ( toolCall . function . arguments )
298315
299316 const tool = request . tools ?. find ( ( t ) => t . id === toolName )
300- if ( ! tool ) continue
317+ if ( ! tool ) {
318+ logger . warn ( 'XAI Provider - Tool not found:' , { toolName } )
319+ continue
320+ }
301321
302322 const toolCallStartTime = Date . now ( )
303323 const mergedArgs = {
@@ -309,7 +329,13 @@ export const xAIProvider: ProviderConfig = {
309329 const toolCallEndTime = Date . now ( )
310330 const toolCallDuration = toolCallEndTime - toolCallStartTime
311331
312- if ( ! result . success ) continue
332+ if ( ! result . success ) {
333+ logger . warn ( 'XAI Provider - Tool execution failed:' , {
334+ toolName,
335+ error : result . error ,
336+ } )
337+ continue
338+ }
313339
314340 // Add to time segments
315341 timeSegments . push ( {
@@ -351,18 +377,19 @@ export const xAIProvider: ProviderConfig = {
351377 content : JSON . stringify ( result . output ) ,
352378 } )
353379 } catch ( error ) {
354- logger . error ( 'Error processing tool call:' , { error } )
380+ logger . error ( 'XAI Provider - Error processing tool call:' , {
381+ error : error instanceof Error ? error . message : String ( error ) ,
382+ toolCall : toolCall . function . name ,
383+ } )
355384 }
356385 }
357386
358387 // Calculate tool call time for this iteration
359388 const thisToolsTime = Date . now ( ) - toolsStartTime
360389 toolsTime += thisToolsTime
361390
362- const nextPayload = {
363- ...payload ,
364- messages : currentMessages ,
365- }
391+ // After tool calls, create next payload based on whether we need more tools or final response
392+ let nextPayload : any
366393
367394 // Update tool_choice based on which forced tools have been used
368395 if (
@@ -374,16 +401,41 @@ export const xAIProvider: ProviderConfig = {
374401 const remainingTools = forcedTools . filter ( ( tool ) => ! usedForcedTools . includes ( tool ) )
375402
376403 if ( remainingTools . length > 0 ) {
377- // Force the next tool
378- nextPayload . tool_choice = {
379- type : 'function' ,
380- function : { name : remainingTools [ 0 ] } ,
404+ // Force the next tool - continue with tools, no response format
405+ nextPayload = {
406+ ...basePayload ,
407+ messages : currentMessages ,
408+ tools : preparedTools ?. tools ,
409+ tool_choice : {
410+ type : 'function' ,
411+ function : { name : remainingTools [ 0 ] } ,
412+ } ,
381413 }
382- logger . info ( `Forcing next tool: ${ remainingTools [ 0 ] } ` )
383414 } else {
384- // All forced tools have been used, switch to auto
385- nextPayload . tool_choice = 'auto'
386- logger . info ( 'All forced tools have been used, switching to auto tool_choice' )
415+ // All forced tools have been used, check if we need response format for final response
416+ if ( request . responseFormat ) {
417+ nextPayload = createResponseFormatPayload ( currentMessages )
418+ } else {
419+ nextPayload = {
420+ ...basePayload ,
421+ messages : currentMessages ,
422+ tool_choice : 'auto' ,
423+ tools : preparedTools ?. tools ,
424+ }
425+ }
426+ }
427+ } else {
428+ // Normal tool processing - check if this might be the final response
429+ if ( request . responseFormat ) {
430+ // Use response format for what might be the final response
431+ nextPayload = createResponseFormatPayload ( currentMessages )
432+ } else {
433+ nextPayload = {
434+ ...basePayload ,
435+ messages : currentMessages ,
436+ tools : preparedTools ?. tools ,
437+ tool_choice : 'auto' ,
438+ }
387439 }
388440 }
389441
@@ -393,7 +445,9 @@ export const xAIProvider: ProviderConfig = {
393445 currentResponse = await xai . chat . completions . create ( nextPayload )
394446
395447 // Check if any forced tools were used in this response
396- checkForForcedToolUsage ( currentResponse , nextPayload . tool_choice )
448+ if ( nextPayload . tool_choice && typeof nextPayload . tool_choice === 'object' ) {
449+ checkForForcedToolUsage ( currentResponse , nextPayload . tool_choice )
450+ }
397451
398452 const nextModelEndTime = Date . now ( )
399453 const thisModelTime = nextModelEndTime - nextModelStartTime
@@ -423,23 +477,35 @@ export const xAIProvider: ProviderConfig = {
423477 iterationCount ++
424478 }
425479 } catch ( error ) {
426- logger . error ( 'Error in xAI request:' , { error } )
480+ logger . error ( 'XAI Provider - Error in tool processing loop:' , {
481+ error : error instanceof Error ? error . message : String ( error ) ,
482+ iterationCount,
483+ } )
427484 }
428485
429486 // After all tool processing complete, if streaming was requested and we have messages, use streaming for the final response
430487 if ( request . stream && iterationCount > 0 ) {
431- logger . info ( 'Using streaming for final XAI response after tool calls' )
432-
433- // When streaming after tool calls with forced tools, make sure tool_choice is set to 'auto'
434- // This prevents the API from trying to force tool usage again in the final streaming response
435- const streamingPayload = {
436- ...payload ,
437- messages : currentMessages ,
438- tool_choice : 'auto' , // Always use 'auto' for the streaming response after tool calls
439- stream : true ,
488+ // For final streaming response, choose between tools (auto) or response_format (never both)
489+ let finalStreamingPayload : any
490+
491+ if ( request . responseFormat ) {
492+ // Use response format, no tools
493+ finalStreamingPayload = {
494+ ...createResponseFormatPayload ( currentMessages ) ,
495+ stream : true ,
496+ }
497+ } else {
498+ // Use tools with auto choice
499+ finalStreamingPayload = {
500+ ...basePayload ,
501+ messages : currentMessages ,
502+ tool_choice : 'auto' ,
503+ tools : preparedTools ?. tools ,
504+ stream : true ,
505+ }
440506 }
441507
442- const streamResponse = await xai . chat . completions . create ( streamingPayload )
508+ const streamResponse = await xai . chat . completions . create ( finalStreamingPayload )
443509
444510 // Create a StreamingExecution response with all collected data
445511 const streamingResult = {
@@ -498,6 +564,14 @@ export const xAIProvider: ProviderConfig = {
498564 const providerEndTimeISO = new Date ( providerEndTime ) . toISOString ( )
499565 const totalDuration = providerEndTime - providerStartTime
500566
567+ logger . info ( 'XAI Provider - Request completed:' , {
568+ totalDuration,
569+ iterationCount : iterationCount + 1 ,
570+ toolCallCount : toolCalls . length ,
571+ hasContent : ! ! content ,
572+ contentLength : content ?. length || 0 ,
573+ } )
574+
501575 return {
502576 content,
503577 model : request . model ,
@@ -521,7 +595,12 @@ export const xAIProvider: ProviderConfig = {
521595 const providerEndTimeISO = new Date ( providerEndTime ) . toISOString ( )
522596 const totalDuration = providerEndTime - providerStartTime
523597
524- logger . error ( 'Error in xAI request:' , { error, duration : totalDuration } )
598+ logger . error ( 'XAI Provider - Request failed:' , {
599+ error : error instanceof Error ? error . message : String ( error ) ,
600+ duration : totalDuration ,
601+ hasTools : ! ! tools ?. length ,
602+ hasResponseFormat : ! ! request . responseFormat ,
603+ } )
525604
526605 // Create a new error with timing information
527606 const enhancedError = new Error ( error instanceof Error ? error . message : String ( error ) )
0 commit comments