@@ -249,6 +249,34 @@ describe("OpenAiNativeHandler", () => {
249249 expect ( modelInfo . info . supportsReasoningEffort ) . toEqual ( [ "low" , "medium" , "high" , "xhigh" ] )
250250 } )
251251
252+ it ( "should return GPT-5.4 model info when selected" , ( ) => {
253+ const gpt54Handler = new OpenAiNativeHandler ( {
254+ ...mockOptions ,
255+ apiModelId : "gpt-5.4" ,
256+ } )
257+
258+ const modelInfo = gpt54Handler . getModel ( )
259+ expect ( modelInfo . id ) . toBe ( "gpt-5.4" )
260+ expect ( modelInfo . info . maxTokens ) . toBe ( 128000 )
261+ expect ( modelInfo . info . contextWindow ) . toBe ( 1_050_000 )
262+ expect ( modelInfo . info . supportsVerbosity ) . toBe ( true )
263+ expect ( modelInfo . info . supportsReasoningEffort ) . toEqual ( [ "none" , "low" , "medium" , "high" , "xhigh" ] )
264+ expect ( modelInfo . info . reasoningEffort ) . toBe ( "none" )
265+ } )
266+
267+ it ( "should return GPT-5.3 Chat model info when selected" , ( ) => {
268+ const chatHandler = new OpenAiNativeHandler ( {
269+ ...mockOptions ,
270+ apiModelId : "gpt-5.3-chat-latest" ,
271+ } )
272+
273+ const modelInfo = chatHandler . getModel ( )
274+ expect ( modelInfo . id ) . toBe ( "gpt-5.3-chat-latest" )
275+ expect ( modelInfo . info . maxTokens ) . toBe ( 16_384 )
276+ expect ( modelInfo . info . contextWindow ) . toBe ( 128000 )
277+ expect ( modelInfo . info . supportsImages ) . toBe ( true )
278+ } )
279+
252280 it ( "should handle undefined model ID" , ( ) => {
253281 const handlerWithoutModel = new OpenAiNativeHandler ( {
254282 openAiNativeApiKey : "test-api-key" ,
@@ -345,6 +373,107 @@ describe("OpenAiNativeHandler", () => {
345373 expect ( textChunks [ 1 ] . text ) . toBe ( " world" )
346374 } )
347375
376+ it ( "should handle GPT-5.4 model with Responses API" , async ( ) => {
377+ const mockFetch = vitest . fn ( ) . mockResolvedValue ( {
378+ ok : true ,
379+ body : new ReadableStream ( {
380+ start ( controller ) {
381+ controller . enqueue (
382+ new TextEncoder ( ) . encode (
383+ 'data: {"type":"response.output_item.added","item":{"type":"text","text":"GPT-5.4 reply"}}\n\n' ,
384+ ) ,
385+ )
386+ controller . enqueue ( new TextEncoder ( ) . encode ( "data: [DONE]\n\n" ) )
387+ controller . close ( )
388+ } ,
389+ } ) ,
390+ } )
391+ global . fetch = mockFetch as any
392+
393+ mockResponsesCreate . mockRejectedValue ( new Error ( "SDK not available" ) )
394+
395+ handler = new OpenAiNativeHandler ( {
396+ ...mockOptions ,
397+ apiModelId : "gpt-5.4" ,
398+ } )
399+
400+ const stream = handler . createMessage ( systemPrompt , messages )
401+ const chunks : any [ ] = [ ]
402+ for await ( const chunk of stream ) {
403+ chunks . push ( chunk )
404+ }
405+
406+ expect ( mockFetch ) . toHaveBeenCalledWith (
407+ "https://api.openai.com/v1/responses" ,
408+ expect . objectContaining ( {
409+ body : expect . any ( String ) ,
410+ } ) ,
411+ )
412+ const body = ( mockFetch . mock . calls [ 0 ] [ 1 ] as any ) . body as string
413+ const parsedBody = JSON . parse ( body )
414+ expect ( parsedBody . model ) . toBe ( "gpt-5.4" )
415+ expect ( parsedBody . max_output_tokens ) . toBe ( 128000 )
416+ expect ( parsedBody . temperature ) . toBeUndefined ( )
417+ expect ( parsedBody . include ) . toEqual ( [ "reasoning.encrypted_content" ] )
418+ expect ( parsedBody . reasoning ?. effort ) . toBe ( "none" )
419+ expect ( parsedBody . text ?. verbosity ) . toBe ( "medium" )
420+
421+ const textChunks = chunks . filter ( ( chunk ) => chunk . type === "text" )
422+ expect ( textChunks ) . toHaveLength ( 1 )
423+ expect ( textChunks [ 0 ] . text ) . toBe ( "GPT-5.4 reply" )
424+ } )
425+
426+ it ( "should handle GPT-5.3 Chat model with Responses API" , async ( ) => {
427+ // Mock fetch for Responses API
428+ const mockFetch = vitest . fn ( ) . mockResolvedValue ( {
429+ ok : true ,
430+ body : new ReadableStream ( {
431+ start ( controller ) {
432+ controller . enqueue (
433+ new TextEncoder ( ) . encode (
434+ 'data: {"type":"response.output_item.added","item":{"type":"text","text":"Chat reply"}}\n\n' ,
435+ ) ,
436+ )
437+ controller . enqueue ( new TextEncoder ( ) . encode ( "data: [DONE]\n\n" ) )
438+ controller . close ( )
439+ } ,
440+ } ) ,
441+ } )
442+ global . fetch = mockFetch as any
443+
444+ // Mock SDK to fail so it uses fetch
445+ mockResponsesCreate . mockRejectedValue ( new Error ( "SDK not available" ) )
446+
447+ handler = new OpenAiNativeHandler ( {
448+ ...mockOptions ,
449+ apiModelId : "gpt-5.3-chat-latest" ,
450+ } )
451+
452+ const stream = handler . createMessage ( systemPrompt , messages )
453+ const chunks : any [ ] = [ ]
454+ for await ( const chunk of stream ) {
455+ chunks . push ( chunk )
456+ }
457+
458+ expect ( mockFetch ) . toHaveBeenCalledWith (
459+ "https://api.openai.com/v1/responses" ,
460+ expect . objectContaining ( {
461+ body : expect . any ( String ) ,
462+ } ) ,
463+ )
464+ const body = ( mockFetch . mock . calls [ 0 ] [ 1 ] as any ) . body as string
465+ const parsedBody = JSON . parse ( body )
466+ expect ( parsedBody . model ) . toBe ( "gpt-5.3-chat-latest" )
467+ expect ( parsedBody . max_output_tokens ) . toBe ( 16_384 )
468+ expect ( parsedBody . temperature ) . toBe ( 0 )
469+ expect ( parsedBody . reasoning ?. effort ) . toBeUndefined ( )
470+ expect ( parsedBody . text ?. verbosity ) . toBeUndefined ( )
471+
472+ const textChunks = chunks . filter ( ( chunk ) => chunk . type === "text" )
473+ expect ( textChunks ) . toHaveLength ( 1 )
474+ expect ( textChunks [ 0 ] . text ) . toBe ( "Chat reply" )
475+ } )
476+
348477 it ( "should handle GPT-5-mini model with Responses API" , async ( ) => {
349478 // Mock fetch for Responses API
350479 const mockFetch = vitest . fn ( ) . mockResolvedValue ( {
0 commit comments