@@ -376,47 +376,55 @@ export class OpenAiHandler extends BaseProvider implements SingleCompletionHandl
376376
377377 // Use Responses API when selected (non-streaming convenience method)
378378 if ( flavor === "responses" ) {
379- // Build a single-turn formatted string input (Developer/User style) for Responses API
380- const formattedInput = this . _formatResponsesSingleMessage (
381- {
382- role : "user" ,
383- content : [ { type : "text" , text : prompt } ] ,
384- } as Anthropic . Messages . MessageParam ,
385- /*includeRole*/ true ,
386- )
379+ // Build structured single-turn input
387380 const payload : Record < string , unknown > = {
388381 model : model . id ,
389- input : formattedInput ,
382+ input : [
383+ {
384+ role : "user" ,
385+ content : [ { type : "input_text" , text : prompt } ] ,
386+ } ,
387+ ] ,
388+ stream : false ,
389+ store : false ,
390390 }
391391
392- // Reasoning effort (Responses )
392+ // Reasoning effort (support "minimal"; include summary: "auto" unless disabled )
393393 const effort = ( this . options . reasoningEffort || model . reasoningEffort ) as
394394 | "minimal"
395395 | "low"
396396 | "medium"
397397 | "high"
398398 | undefined
399- if ( this . options . enableReasoningEffort && effort && effort !== "minimal" ) {
400- payload . reasoning = { effort }
399+ if ( this . options . enableReasoningEffort && effort ) {
400+ ; (
401+ payload as { reasoning ?: { effort : "minimal" | "low" | "medium" | "high" ; summary ?: "auto" } }
402+ ) . reasoning = {
403+ effort,
404+ ...( this . options . enableGpt5ReasoningSummary !== false ? { summary : "auto" as const } : { } ) ,
405+ }
401406 }
402407
403- // Temperature if set
404- if ( this . options . modelTemperature !== undefined ) {
405- payload . temperature = this . options . modelTemperature
408+ // Temperature if supported and set
409+ if ( modelInfo . supportsTemperature !== false && this . options . modelTemperature !== undefined ) {
410+ ; ( payload as Record < string , unknown > ) . temperature = this . options . modelTemperature
406411 }
407412
408- // Verbosity via text.verbosity - include only when explicitly specified
409- if ( this . options . verbosity ) {
410- payload . text = { verbosity : this . options . verbosity as "low" | "medium" | "high" }
413+ // Verbosity via text.verbosity - include only when supported
414+ if ( this . options . verbosity && modelInfo . supportsVerbosity ) {
415+ ; ( payload as { text ?: { verbosity : "low" | "medium" | "high" } } ) . text = {
416+ verbosity : this . options . verbosity as "low" | "medium" | "high" ,
417+ }
411418 }
412419
413420 // max_output_tokens
414421 if ( this . options . includeMaxTokens === true ) {
415- payload . max_output_tokens = this . options . modelMaxTokens || modelInfo . maxTokens
422+ ; ( payload as Record < string , unknown > ) . max_output_tokens =
423+ this . options . modelMaxTokens || modelInfo . maxTokens
416424 }
417425
418426 const response = await this . _responsesCreateWithRetries ( payload , {
419- usedArrayInput : false ,
427+ usedArrayInput : true ,
420428 lastUserMessage : undefined ,
421429 previousId : undefined ,
422430 systemPrompt : "" ,
@@ -736,47 +744,40 @@ export class OpenAiHandler extends BaseProvider implements SingleCompletionHandl
736744 const modelId = this . options . openAiModelId ?? ""
737745 const nonStreaming = ! ( this . options . openAiStreamingEnabled ?? true )
738746
739- // Build Responses payload (align with OpenAI Native Responses API formatting)
740- // Azure- and Responses-compatible multimodal handling:
741- // - Use array input ONLY when the latest user message contains images (initial turn)
742- // - When previous_response_id is present, send only the latest user turn:
743- // • Text-only => single string "User: ...", no Developer preface
744- // • With images => one-item array containing only the latest user content (no Developer preface)
745- const lastUserMessage = [ ...messages ] . reverse ( ) . find ( ( m ) => m . role === "user" )
746- const lastUserHasImages =
747- ! ! lastUserMessage &&
748- Array . isArray ( lastUserMessage . content ) &&
749- lastUserMessage . content . some ( ( b : unknown ) => ( b as { type ?: string } | undefined ) ?. type === "image" )
750-
751- // Conversation continuity (parity with OpenAiNativeHandler.prepareGpt5Input)
747+ // Determine conversation continuity id (skip when explicitly suppressed)
752748 const previousId = metadata ?. suppressPreviousResponseId
753749 ? undefined
754750 : ( metadata ?. previousResponseId ?? this . lastResponseId )
755751
752+ // Prepare Responses API input per test expectations:
753+ // - Non-minimal text-only => single string with Developer/User lines
754+ // - Minimal (previous_response_id) => single string "User: ..." when last user has no images
755+ // - Image cases => structured array; inject Developer preface as first item (non-minimal only)
756+ const lastUserMessage = [ ...messages ] . reverse ( ) . find ( ( m ) => m . role === "user" )
757+ const lastUserHasImages =
758+ ! ! lastUserMessage &&
759+ Array . isArray ( lastUserMessage . content ) &&
760+ lastUserMessage . content . some ( ( b : any ) => ( b as any ) ?. type === "image" )
756761 const minimalInputMode = Boolean ( previousId )
757762
758763 let inputPayload : unknown
759764 if ( minimalInputMode && lastUserMessage ) {
760- // Minimal- mode: only the latest user message (no Developer preface)
765+ // Minimal mode: only latest user turn
761766 if ( lastUserHasImages ) {
762- // Single-item array with just the latest user content
763767 inputPayload = this . _toResponsesInput ( [ lastUserMessage ] )
764768 } else {
765- // Single message string "User: ..."
766769 inputPayload = this . _formatResponsesSingleMessage ( lastUserMessage , true )
767770 }
768771 } else if ( lastUserHasImages && lastUserMessage ) {
769- // Initial turn with images: include Developer preface and minimal prior context to preserve continuity
772+ // Initial turn with images: include Developer preface and minimal context
770773 const lastAssistantMessage = [ ...messages ] . reverse ( ) . find ( ( m ) => m . role === "assistant" )
771-
772774 const messagesForArray = messages . filter ( ( m ) => {
773775 if ( m . role === "assistant" ) {
774776 return lastAssistantMessage ? m === lastAssistantMessage : false
775777 }
776778 if ( m . role === "user" ) {
777779 const hasImage =
778- Array . isArray ( m . content ) &&
779- m . content . some ( ( b : unknown ) => ( b as { type ?: string } | undefined ) ?. type === "image" )
780+ Array . isArray ( m . content ) && m . content . some ( ( b : any ) => ( b as any ) ?. type === "image" )
780781 return hasImage || m === lastUserMessage
781782 }
782783 return false
@@ -789,19 +790,20 @@ export class OpenAiHandler extends BaseProvider implements SingleCompletionHandl
789790 }
790791 inputPayload = [ developerPreface , ...arrayInput ]
791792 } else {
792- // Pure text history: full compact transcript (includes both user and assistant turns)
793+ // Pure text history: compact transcript string
793794 inputPayload = this . _formatResponsesInput ( systemPrompt , messages )
794795 }
795- const usedArrayInput = Array . isArray ( inputPayload )
796796
797+ // Build base payload: use top-level instructions; default to storing unless explicitly disabled
797798 const basePayload : Record < string , unknown > = {
798799 model : modelId ,
799800 input : inputPayload ,
800801 ...( previousId ? { previous_response_id : previousId } : { } ) ,
802+ instructions : systemPrompt ,
803+ store : metadata ?. store !== false ,
801804 }
802805
803- // Reasoning effort (Responses expects: reasoning: { effort, summary? })
804- // Parity with native: support "minimal" and include summary: "auto" unless explicitly disabled
806+ // Reasoning effort (support "minimal"; include summary: "auto" unless disabled)
805807 if ( this . options . enableReasoningEffort && ( this . options . reasoningEffort || openAiParams ?. reasoningEffort ) ) {
806808 const effort = ( this . options . reasoningEffort || openAiParams ?. reasoningEffort ) as
807809 | "minimal"
@@ -811,25 +813,25 @@ export class OpenAiHandler extends BaseProvider implements SingleCompletionHandl
811813 | undefined
812814 if ( effort ) {
813815 ; (
814- basePayload as {
815- reasoning ?: { effort : "minimal" | "low" | "medium" | "high" ; summary ?: "auto" }
816- }
816+ basePayload as { reasoning ?: { effort : "minimal" | "low" | "medium" | "high" ; summary ?: "auto" } }
817817 ) . reasoning = {
818818 effort,
819819 ...( this . options . enableGpt5ReasoningSummary !== false ? { summary : "auto" as const } : { } ) ,
820820 }
821821 }
822822 }
823823
824- // Temperature (only include when explicitly set by the user)
824+ // Temperature: include only if model supports it
825825 const deepseekReasoner = modelId . includes ( "deepseek-reasoner" ) || ( this . options . openAiR1FormatEnabled ?? false )
826- if ( this . options . modelTemperature !== undefined ) {
827- basePayload . temperature = this . options . modelTemperature
828- } else if ( deepseekReasoner ) {
829- basePayload . temperature = DEEP_SEEK_DEFAULT_TEMPERATURE
826+ if ( modelInfo . supportsTemperature !== false ) {
827+ if ( this . options . modelTemperature !== undefined ) {
828+ ; ( basePayload as Record < string , unknown > ) . temperature = this . options . modelTemperature
829+ } else if ( deepseekReasoner ) {
830+ ; ( basePayload as Record < string , unknown > ) . temperature = DEEP_SEEK_DEFAULT_TEMPERATURE
831+ }
830832 }
831833
832- // Verbosity: include only when explicitly specified in settings
834+ // Verbosity: include when provided; retry logic removes it on 400
833835 if ( this . options . verbosity ) {
834836 ; ( basePayload as { text ?: { verbosity : "low" | "medium" | "high" } } ) . text = {
835837 verbosity : this . options . verbosity as "low" | "medium" | "high" ,
@@ -844,7 +846,7 @@ export class OpenAiHandler extends BaseProvider implements SingleCompletionHandl
844846 // Non-streaming path
845847 if ( nonStreaming ) {
846848 const response = await this . _responsesCreateWithRetries ( basePayload , {
847- usedArrayInput,
849+ usedArrayInput : Array . isArray ( inputPayload ) ,
848850 lastUserMessage,
849851 previousId,
850852 systemPrompt,
@@ -857,7 +859,7 @@ export class OpenAiHandler extends BaseProvider implements SingleCompletionHandl
857859 // Streaming path (auto-fallback to non-streaming result if provider ignores stream flag)
858860 const streamingPayload : Record < string , unknown > = { ...basePayload , stream : true }
859861 const maybeStream = await this . _responsesCreateWithRetries ( streamingPayload , {
860- usedArrayInput,
862+ usedArrayInput : Array . isArray ( inputPayload ) ,
861863 lastUserMessage,
862864 previousId,
863865 systemPrompt,
@@ -925,30 +927,53 @@ export class OpenAiHandler extends BaseProvider implements SingleCompletionHandl
925927
926928 private _toResponsesInput ( anthropicMessages : Anthropic . Messages . MessageParam [ ] ) : Array < {
927929 role : "user" | "assistant"
928- content : Array < { type : "input_text" ; text : string } | { type : "input_image" ; image_url : string } >
930+ content : Array <
931+ | { type : "input_text" ; text : string }
932+ | { type : "input_image" ; image_url : string }
933+ | { type : "output_text" ; text : string }
934+ >
929935 } > {
930936 const input : Array < {
931937 role : "user" | "assistant"
932- content : Array < { type : "input_text" ; text : string } | { type : "input_image" ; image_url : string } >
938+ content : Array <
939+ | { type : "input_text" ; text : string }
940+ | { type : "input_image" ; image_url : string }
941+ | { type : "output_text" ; text : string }
942+ >
933943 } > = [ ]
934944
935945 for ( const msg of anthropicMessages ) {
936946 const role = msg . role === "assistant" ? "assistant" : "user"
937- const parts : Array < { type : "input_text" ; text : string } | { type : "input_image" ; image_url : string } > = [ ]
947+ const parts : Array <
948+ | { type : "input_text" ; text : string }
949+ | { type : "input_image" ; image_url : string }
950+ | { type : "output_text" ; text : string }
951+ > = [ ]
938952
939953 if ( typeof msg . content === "string" ) {
940954 if ( msg . content . length > 0 ) {
941- parts . push ( { type : "input_text" , text : msg . content } )
955+ if ( role === "assistant" ) {
956+ parts . push ( { type : "output_text" , text : msg . content } )
957+ } else {
958+ parts . push ( { type : "input_text" , text : msg . content } )
959+ }
942960 }
943- } else {
961+ } else if ( Array . isArray ( msg . content ) ) {
944962 for ( const block of msg . content ) {
945963 if ( block . type === "text" ) {
946- parts . push ( { type : "input_text" , text : block . text } )
964+ if ( role === "assistant" ) {
965+ parts . push ( { type : "output_text" , text : block . text } )
966+ } else {
967+ parts . push ( { type : "input_text" , text : block . text } )
968+ }
947969 } else if ( block . type === "image" ) {
948- parts . push ( {
949- type : "input_image" ,
950- image_url : `data:${ block . source . media_type } ;base64,${ block . source . data } ` ,
951- } )
970+ // Images are treated as user input; ignore images on assistant turns
971+ if ( role === "user" ) {
972+ parts . push ( {
973+ type : "input_image" ,
974+ image_url : `data:${ block . source . media_type } ;base64,${ block . source . data } ` ,
975+ } )
976+ }
952977 }
953978 // tool_use/tool_result are omitted in this minimal mapping (can be added as needed)
954979 }
0 commit comments