@@ -286,175 +286,41 @@ export class OpenAiHandler extends BaseProvider implements SingleCompletionHandl
286286
287287 // Non-streaming path
288288 if ( nonStreaming ) {
289- try {
290- const response = await (
291- this . client as unknown as {
292- responses : { create : ( body : Record < string , unknown > ) => Promise < unknown > }
293- }
294- ) . responses . create ( basePayload )
295- yield * this . _yieldResponsesResult ( response as unknown , modelInfo )
296- } catch ( err : unknown ) {
297- // Retry without previous_response_id if server rejects it (400 "Previous response ... not found")
298- if ( previousId && this . _isPreviousResponseNotFoundError ( err ) ) {
299- const { previous_response_id : _omitPrev , ...withoutPrev } = basePayload as {
300- previous_response_id ?: unknown
301- [ key : string ] : unknown
302- }
303- // Clear stored continuity to avoid reusing a bad id
304- this . lastResponseId = undefined
305- const response = await (
306- this . client as unknown as {
307- responses : { create : ( body : Record < string , unknown > ) => Promise < unknown > }
308- }
309- ) . responses . create ( withoutPrev )
310- yield * this . _yieldResponsesResult ( response as unknown , modelInfo )
311- }
312- // Graceful downgrade if verbosity is rejected by server (400 unknown/unsupported parameter)
313- else if ( "text" in basePayload && this . _isVerbosityUnsupportedError ( err ) ) {
314- // Remove text.verbosity and retry once
315- const { text : _omit , ...withoutVerbosity } = basePayload as { text ?: unknown } & Record <
316- string ,
317- unknown
318- >
319- const response = await (
320- this . client as unknown as {
321- responses : { create : ( body : Record < string , unknown > ) => Promise < unknown > }
322- }
323- ) . responses . create ( withoutVerbosity )
324- yield * this . _yieldResponsesResult ( response as unknown , modelInfo )
325- } else if ( usedArrayInput && this . _isInputTextInvalidError ( err ) ) {
326- // Azure-specific fallback: retry with a minimal single-message string when array input is rejected
327- const retryPayload : Record < string , unknown > = {
328- ...basePayload ,
329- input :
330- previousId && lastUserMessage
331- ? this . _formatResponsesSingleMessage ( lastUserMessage , true )
332- : this . _formatResponsesInput ( systemPrompt , messages ) ,
333- }
334- const response = await (
335- this . client as unknown as {
336- responses : { create : ( body : Record < string , unknown > ) => Promise < unknown > }
337- }
338- ) . responses . create ( retryPayload )
339- yield * this . _yieldResponsesResult ( response as unknown , modelInfo )
340- } else {
341- throw err
342- }
343- }
289+ const response = await this . _responsesCreateWithRetries ( basePayload , {
290+ usedArrayInput,
291+ lastUserMessage,
292+ previousId,
293+ systemPrompt,
294+ messages,
295+ } )
296+ yield * this . _yieldResponsesResult ( response as unknown , modelInfo )
344297 return
345298 }
346299
347300 // Streaming path (auto-fallback to non-streaming result if provider ignores stream flag)
348301 const streamingPayload : Record < string , unknown > = { ...basePayload , stream : true }
349- try {
350- const maybeStream = await (
351- this . client as unknown as {
352- responses : { create : ( body : Record < string , unknown > ) => Promise < unknown > }
353- }
354- ) . responses . create ( streamingPayload )
355-
356- const isAsyncIterable = ( obj : unknown ) : obj is AsyncIterable < unknown > =>
357- typeof ( obj as AsyncIterable < unknown > ) [ Symbol . asyncIterator ] === "function"
358-
359- if ( isAsyncIterable ( maybeStream ) ) {
360- for await ( const chunk of handleResponsesStream ( maybeStream , {
361- onResponseId : ( id ) => {
362- this . lastResponseId = id
363- } ,
364- } ) ) {
365- yield chunk
366- }
367- } else {
368- // Some providers may ignore the stream flag and return a complete response
369- yield * this . _yieldResponsesResult ( maybeStream as unknown , modelInfo )
370- }
371- } catch ( err : unknown ) {
372- // Retry without previous_response_id if server rejects it (400 "Previous response ... not found")
373- if ( previousId && this . _isPreviousResponseNotFoundError ( err ) ) {
374- const { previous_response_id : _omitPrev , ...withoutPrev } = streamingPayload as {
375- previous_response_id ?: unknown
376- [ key : string ] : unknown
377- }
378- this . lastResponseId = undefined
379- const maybeStreamRetry = await (
380- this . client as unknown as {
381- responses : { create : ( body : Record < string , unknown > ) => Promise < unknown > }
382- }
383- ) . responses . create ( withoutPrev )
384-
385- const isAsyncIterable = ( obj : unknown ) : obj is AsyncIterable < unknown > =>
386- typeof ( obj as AsyncIterable < unknown > ) [ Symbol . asyncIterator ] === "function"
387-
388- if ( isAsyncIterable ( maybeStreamRetry ) ) {
389- for await ( const chunk of handleResponsesStream ( maybeStreamRetry , {
390- onResponseId : ( id ) => {
391- this . lastResponseId = id
392- } ,
393- } ) ) {
394- yield chunk
395- }
396- } else {
397- yield * this . _yieldResponsesResult ( maybeStreamRetry as unknown , modelInfo )
398- }
399- }
400- // Graceful verbosity removal on 400
401- else if ( "text" in streamingPayload && this . _isVerbosityUnsupportedError ( err ) ) {
402- const { text : _omit , ...withoutVerbosity } = streamingPayload as { text ?: unknown } & Record <
403- string ,
404- unknown
405- >
406- const maybeStreamRetry = await (
407- this . client as unknown as {
408- responses : { create : ( body : Record < string , unknown > ) => Promise < unknown > }
409- }
410- ) . responses . create ( withoutVerbosity )
411-
412- const isAsyncIterable = ( obj : unknown ) : obj is AsyncIterable < unknown > =>
413- typeof ( obj as AsyncIterable < unknown > ) [ Symbol . asyncIterator ] === "function"
414-
415- if ( isAsyncIterable ( maybeStreamRetry ) ) {
416- for await ( const chunk of handleResponsesStream ( maybeStreamRetry , {
417- onResponseId : ( id ) => {
418- this . lastResponseId = id
419- } ,
420- } ) ) {
421- yield chunk
422- }
423- } else {
424- yield * this . _yieldResponsesResult ( maybeStreamRetry as unknown , modelInfo )
425- }
426- } else if ( usedArrayInput && this . _isInputTextInvalidError ( err ) ) {
427- // Azure-specific fallback for streaming: retry with minimal single-message string while keeping stream: true
428- const retryStreamingPayload : Record < string , unknown > = {
429- ...streamingPayload ,
430- input :
431- previousId && lastUserMessage
432- ? this . _formatResponsesSingleMessage ( lastUserMessage , true )
433- : this . _formatResponsesInput ( systemPrompt , messages ) ,
434- }
435- const maybeStreamRetry = await (
436- this . client as unknown as {
437- responses : { create : ( body : Record < string , unknown > ) => Promise < unknown > }
438- }
439- ) . responses . create ( retryStreamingPayload )
302+ const maybeStream = await this . _responsesCreateWithRetries ( streamingPayload , {
303+ usedArrayInput,
304+ lastUserMessage,
305+ previousId,
306+ systemPrompt,
307+ messages,
308+ } )
440309
441- const isAsyncIterable = ( obj : unknown ) : obj is AsyncIterable < unknown > =>
442- typeof ( obj as AsyncIterable < unknown > ) [ Symbol . asyncIterator ] === "function"
310+ const isAsyncIterable = ( obj : unknown ) : obj is AsyncIterable < unknown > =>
311+ typeof ( obj as AsyncIterable < unknown > ) [ Symbol . asyncIterator ] === "function"
443312
444- if ( isAsyncIterable ( maybeStreamRetry ) ) {
445- for await ( const chunk of handleResponsesStream ( maybeStreamRetry , {
446- onResponseId : ( id ) => {
447- this . lastResponseId = id
448- } ,
449- } ) ) {
450- yield chunk
451- }
452- } else {
453- yield * this . _yieldResponsesResult ( maybeStreamRetry as unknown , modelInfo )
454- }
455- } else {
456- throw err
313+ if ( isAsyncIterable ( maybeStream ) ) {
314+ for await ( const chunk of handleResponsesStream ( maybeStream , {
315+ onResponseId : ( id ) => {
316+ this . lastResponseId = id
317+ } ,
318+ } ) ) {
319+ yield chunk
457320 }
321+ } else {
322+ // Some providers may ignore the stream flag and return a complete response
323+ yield * this . _yieldResponsesResult ( maybeStream as unknown , modelInfo )
458324 }
459325 return
460326 }
@@ -686,25 +552,22 @@ export class OpenAiHandler extends BaseProvider implements SingleCompletionHandl
686552 payload . max_output_tokens = this . options . modelMaxTokens || modelInfo . maxTokens
687553 }
688554
555+ const response = await this . _responsesCreateWithRetries ( payload as unknown as Record < string , unknown > , {
556+ usedArrayInput : false ,
557+ lastUserMessage : undefined ,
558+ previousId : undefined ,
559+ systemPrompt : "" ,
560+ messages : [ ] ,
561+ } )
689562 try {
690- const response = await this . client . responses . create ( payload )
691- try {
692- const respId = ( response as { id ?: unknown } | undefined ) ?. id
693- if ( typeof respId === "string" && respId . length > 0 ) {
694- this . lastResponseId = respId
695- }
696- } catch {
697- // ignore
698- }
699- return this . _extractResponsesText ( response ) ?? ""
700- } catch ( err : unknown ) {
701- if ( payload . text && this . _isVerbosityUnsupportedError ( err ) ) {
702- const { text : _omit , ...withoutVerbosity } = payload
703- const response = await this . client . responses . create ( withoutVerbosity )
704- return this . _extractResponsesText ( response ) ?? ""
563+ const respId = ( response as { id ?: unknown } | undefined ) ?. id
564+ if ( typeof respId === "string" && respId . length > 0 ) {
565+ this . lastResponseId = respId
705566 }
706- throw err
567+ } catch {
568+ // ignore
707569 }
570+ return this . _extractResponsesText ( response ) ?? ""
708571 }
709572
710573 const requestOptions : OpenAI . Chat . Completions . ChatCompletionCreateParamsNonStreaming = {
@@ -1095,6 +958,65 @@ export class OpenAiHandler extends BaseProvider implements SingleCompletionHandl
1095958 const msgRaw = ( anyErr . message ?? anyErr . error ?. message ?? "" ) . toString ( ) . toLowerCase ( )
1096959 return status === 400 && msgRaw . includes ( "invalid value" ) && msgRaw . includes ( "input_text" )
1097960 }
961+
962+ /**
963+ * Centralized Responses.create with one-shot retries for common provider errors:
964+ * - 400 "Previous response ... not found" -> drop previous_response_id and retry
965+ * - 400 unknown/unsupported "text.verbosity" -> remove text and retry
966+ * - 400 invalid value for input_text (Azure) -> rebuild single-message string input and retry
967+ * Returns either an AsyncIterable (streaming) or a full response object (non-streaming).
968+ */
969+ private async _responsesCreateWithRetries (
970+ payload : Record < string , unknown > ,
971+ opts : {
972+ usedArrayInput : boolean
973+ lastUserMessage ?: Anthropic . Messages . MessageParam
974+ previousId ?: string
975+ systemPrompt : string
976+ messages : Anthropic . Messages . MessageParam [ ]
977+ } ,
978+ ) : Promise < unknown > {
979+ const create = ( body : Record < string , unknown > ) =>
980+ (
981+ this . client as unknown as { responses : { create : ( b : Record < string , unknown > ) => Promise < unknown > } }
982+ ) . responses . create ( body )
983+
984+ try {
985+ return await create ( payload )
986+ } catch ( err : unknown ) {
987+ // Retry without previous_response_id if server rejects it
988+ if ( opts . previousId && this . _isPreviousResponseNotFoundError ( err ) ) {
989+ const { previous_response_id : _omitPrev , ...withoutPrev } = payload as {
990+ previous_response_id ?: unknown
991+ [ key : string ] : unknown
992+ }
993+ this . lastResponseId = undefined
994+ return await create ( withoutPrev )
995+ }
996+
997+ // Graceful downgrade if verbosity is rejected by server
998+ if ( "text" in payload && this . _isVerbosityUnsupportedError ( err ) ) {
999+ const { text : _omit , ...withoutVerbosity } = payload as { text ?: unknown } & Record < string , unknown >
1000+ return await create ( withoutVerbosity )
1001+ }
1002+
1003+ // Azure-specific fallback when array input is rejected
1004+ if ( opts . usedArrayInput && this . _isInputTextInvalidError ( err ) ) {
1005+ const fallbackInput =
1006+ opts . previousId && opts . lastUserMessage
1007+ ? this . _formatResponsesSingleMessage ( opts . lastUserMessage , true )
1008+ : this . _formatResponsesInput ( opts . systemPrompt , opts . messages )
1009+
1010+ const retryPayload : Record < string , unknown > = {
1011+ ...payload ,
1012+ input : fallbackInput ,
1013+ }
1014+ return await create ( retryPayload )
1015+ }
1016+
1017+ throw err
1018+ }
1019+ }
10981020 private async * _yieldResponsesResult ( response : any , modelInfo : ModelInfo ) : ApiStream {
10991021 // Capture response id for continuity when present
11001022 try {
0 commit comments