@@ -95,6 +95,49 @@ describe('inference.ts', () => {
95
95
expect ( result ) . toBeNull ( )
96
96
expect ( core . info ) . toHaveBeenCalledWith ( 'Model response: No response content' )
97
97
} )
98
+
99
+ it ( 'includes response format when specified' , async ( ) => {
100
+ const requestWithResponseFormat = {
101
+ ...mockRequest ,
102
+ responseFormat : {
103
+ type : 'json_schema' as const ,
104
+ json_schema : { type : 'object' } ,
105
+ } ,
106
+ }
107
+
108
+ const mockResponse = {
109
+ choices : [
110
+ {
111
+ message : {
112
+ content : '{"result": "success"}' ,
113
+ } ,
114
+ } ,
115
+ ] ,
116
+ }
117
+
118
+ mockCreate . mockResolvedValue ( mockResponse )
119
+
120
+ const result = await simpleInference ( requestWithResponseFormat )
121
+
122
+ expect ( result ) . toBe ( '{"result": "success"}' )
123
+
124
+ // Verify response format was included in the request
125
+ expect ( mockCreate ) . toHaveBeenCalledWith ( {
126
+ messages : [
127
+ {
128
+ role : 'system' ,
129
+ content : 'You are a test assistant' ,
130
+ } ,
131
+ {
132
+ role : 'user' ,
133
+ content : 'Hello, AI!' ,
134
+ } ,
135
+ ] ,
136
+ max_tokens : 100 ,
137
+ model : 'gpt-4' ,
138
+ response_format : requestWithResponseFormat . responseFormat ,
139
+ } )
140
+ } )
98
141
} )
99
142
100
143
describe ( 'mcpInference' , ( ) => {
@@ -140,6 +183,7 @@ describe('inference.ts', () => {
140
183
// eslint-disable-next-line @typescript-eslint/no-explicit-any
141
184
const callArgs = mockCreate . mock . calls [ 0 ] [ 0 ] as any
142
185
expect ( callArgs . tools ) . toEqual ( mockMcpClient . tools )
186
+ expect ( callArgs . response_format ) . toBeUndefined ( )
143
187
expect ( callArgs . model ) . toBe ( 'gpt-4' )
144
188
expect ( callArgs . max_tokens ) . toBe ( 100 )
145
189
} )
@@ -315,5 +359,191 @@ describe('inference.ts', () => {
315
359
316
360
expect ( result ) . toBe ( 'Second message' )
317
361
} )
362
+
363
+ it ( 'makes additional loop with response format when no tool calls are made' , async ( ) => {
364
+ const requestWithResponseFormat = {
365
+ ...mockRequest ,
366
+ responseFormat : {
367
+ type : 'json_schema' as const ,
368
+ json_schema : { type : 'object' } ,
369
+ } ,
370
+ }
371
+
372
+ // First response without tool calls
373
+ const firstResponse = {
374
+ choices : [
375
+ {
376
+ message : {
377
+ content : 'First response' ,
378
+ tool_calls : null ,
379
+ } ,
380
+ } ,
381
+ ] ,
382
+ }
383
+
384
+ // Second response with response format applied
385
+ const secondResponse = {
386
+ choices : [
387
+ {
388
+ message : {
389
+ content : '{"result": "formatted response"}' ,
390
+ tool_calls : null ,
391
+ } ,
392
+ } ,
393
+ ] ,
394
+ }
395
+
396
+ mockCreate . mockResolvedValueOnce ( firstResponse ) . mockResolvedValueOnce ( secondResponse )
397
+
398
+ const result = await mcpInference ( requestWithResponseFormat , mockMcpClient )
399
+
400
+ expect ( result ) . toBe ( '{"result": "formatted response"}' )
401
+ expect ( mockCreate ) . toHaveBeenCalledTimes ( 2 )
402
+ expect ( core . info ) . toHaveBeenCalledWith ( 'Making one more MCP loop with the requested response format...' )
403
+
404
+ // First call should have tools but no response format
405
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
406
+ const firstCall = mockCreate . mock . calls [ 0 ] [ 0 ] as any
407
+ expect ( firstCall . tools ) . toEqual ( mockMcpClient . tools )
408
+ expect ( firstCall . response_format ) . toBeUndefined ( )
409
+
410
+ // Second call should have response format but no tools
411
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
412
+ const secondCall = mockCreate . mock . calls [ 1 ] [ 0 ] as any
413
+ expect ( secondCall . tools ) . toBeUndefined ( )
414
+ expect ( secondCall . response_format ) . toEqual ( requestWithResponseFormat . responseFormat )
415
+
416
+ // Second call should include the user message requesting JSON format
417
+ expect ( secondCall . messages ) . toHaveLength ( 5 ) // system, user, assistant, user, assistant
418
+ expect ( secondCall . messages [ 3 ] . role ) . toBe ( 'user' )
419
+ expect ( secondCall . messages [ 3 ] . content ) . toContain ( 'Please provide your response in the exact' )
420
+ } )
421
+
422
+ it ( 'uses response format only on final iteration after tool calls' , async ( ) => {
423
+ const requestWithResponseFormat = {
424
+ ...mockRequest ,
425
+ responseFormat : {
426
+ type : 'json_schema' as const ,
427
+ json_schema : { type : 'object' } ,
428
+ } ,
429
+ }
430
+
431
+ const toolCalls = [
432
+ {
433
+ id : 'call-123' ,
434
+ function : {
435
+ name : 'test-tool' ,
436
+ arguments : '{"param": "value"}' ,
437
+ } ,
438
+ } ,
439
+ ]
440
+
441
+ const toolResults = [
442
+ {
443
+ tool_call_id : 'call-123' ,
444
+ role : 'tool' ,
445
+ name : 'test-tool' ,
446
+ content : 'Tool result' ,
447
+ } ,
448
+ ]
449
+
450
+ // First response with tool calls
451
+ const firstResponse = {
452
+ choices : [
453
+ {
454
+ message : {
455
+ content : 'Using tool' ,
456
+ tool_calls : toolCalls ,
457
+ } ,
458
+ } ,
459
+ ] ,
460
+ }
461
+
462
+ // Second response without tool calls, but should trigger final message loop
463
+ const secondResponse = {
464
+ choices : [
465
+ {
466
+ message : {
467
+ content : 'Intermediate result' ,
468
+ tool_calls : null ,
469
+ } ,
470
+ } ,
471
+ ] ,
472
+ }
473
+
474
+ // Third response with response format
475
+ const thirdResponse = {
476
+ choices : [
477
+ {
478
+ message : {
479
+ content : '{"final": "result"}' ,
480
+ tool_calls : null ,
481
+ } ,
482
+ } ,
483
+ ] ,
484
+ }
485
+
486
+ mockCreate
487
+ . mockResolvedValueOnce ( firstResponse )
488
+ . mockResolvedValueOnce ( secondResponse )
489
+ . mockResolvedValueOnce ( thirdResponse )
490
+
491
+ mockExecuteToolCalls . mockResolvedValue ( toolResults )
492
+
493
+ const result = await mcpInference ( requestWithResponseFormat , mockMcpClient )
494
+
495
+ expect ( result ) . toBe ( '{"final": "result"}' )
496
+ expect ( mockCreate ) . toHaveBeenCalledTimes ( 3 )
497
+
498
+ // First call: tools but no response format
499
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
500
+ const firstCall = mockCreate . mock . calls [ 0 ] [ 0 ] as any
501
+ expect ( firstCall . tools ) . toEqual ( mockMcpClient . tools )
502
+ expect ( firstCall . response_format ) . toBeUndefined ( )
503
+
504
+ // Second call: tools but no response format (after tool execution)
505
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
506
+ const secondCall = mockCreate . mock . calls [ 1 ] [ 0 ] as any
507
+ expect ( secondCall . tools ) . toEqual ( mockMcpClient . tools )
508
+ expect ( secondCall . response_format ) . toBeUndefined ( )
509
+
510
+ // Third call: response format but no tools (final message)
511
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
512
+ const thirdCall = mockCreate . mock . calls [ 2 ] [ 0 ] as any
513
+ expect ( thirdCall . tools ) . toBeUndefined ( )
514
+ expect ( thirdCall . response_format ) . toEqual ( requestWithResponseFormat . responseFormat )
515
+ } )
516
+
517
+ it ( 'returns immediately when response format is set and finalMessage is already true' , async ( ) => {
518
+ const requestWithResponseFormat = {
519
+ ...mockRequest ,
520
+ responseFormat : {
521
+ type : 'json_schema' as const ,
522
+ json_schema : { type : 'object' } ,
523
+ } ,
524
+ }
525
+
526
+ // Response without tool calls on what would be the final message iteration
527
+ const mockResponse = {
528
+ choices : [
529
+ {
530
+ message : {
531
+ content : '{"immediate": "result"}' ,
532
+ tool_calls : null ,
533
+ } ,
534
+ } ,
535
+ ] ,
536
+ }
537
+
538
+ mockCreate . mockResolvedValue ( mockResponse )
539
+
540
+ // We need to test a scenario where finalMessage would already be true
541
+ // This happens when we're already in the final iteration
542
+ const result = await mcpInference ( requestWithResponseFormat , mockMcpClient )
543
+
544
+ // The function should make two calls: one normal, then one with response format
545
+ expect ( mockCreate ) . toHaveBeenCalledTimes ( 2 )
546
+ expect ( result ) . toBe ( '{"immediate": "result"}' )
547
+ } )
318
548
} )
319
549
} )
0 commit comments