@@ -877,7 +877,11 @@ describe("OpenAI Responses route", () => {
877877 Effect . provide ( fixedResponse ( sseEvents ( { type : "error" , code : "rate_limit_exceeded" , message : "Slow down" } ) ) ) ,
878878 )
879879
880- expect ( response . events ) . toEqual ( [ { type : "provider-error" , message : "Slow down" } ] )
880+ // Prefix the code so consumers see the failure mode, not just the
881+ // sometimes-generic provider message. The bare message alone meant
882+ // production errors like rate limits were indistinguishable from
883+ // unrelated stream failures.
884+ expect ( response . events ) . toEqual ( [ { type : "provider-error" , message : "rate_limit_exceeded: Slow down" } ] )
881885 } ) ,
882886 )
883887
@@ -891,6 +895,103 @@ describe("OpenAI Responses route", () => {
891895 } ) ,
892896 )
893897
898+ it . effect ( "falls back to error code when message is empty" , ( ) =>
899+ Effect . gen ( function * ( ) {
900+ const response = yield * LLMClient . generate ( request ) . pipe (
901+ Effect . provide ( fixedResponse ( sseEvents ( { type : "error" , code : "internal_error" , message : "" } ) ) ) ,
902+ )
903+
904+ expect ( response . events ) . toEqual ( [ { type : "provider-error" , message : "internal_error" } ] )
905+ } ) ,
906+ )
907+
908+ // Regression: `response.failed` carries the failure details under
909+ // `response.error`, not at the top level. The previous handler only
910+ // checked top-level `message`/`code` and so always emitted the bare
911+ // "OpenAI Responses response failed" string, hiding the real cause.
912+ it . effect ( "surfaces response.failed details from response.error" , ( ) =>
913+ Effect . gen ( function * ( ) {
914+ const response = yield * LLMClient . generate ( request ) . pipe (
915+ Effect . provide (
916+ fixedResponse (
917+ sseEvents ( {
918+ type : "response.failed" ,
919+ response : {
920+ id : "resp_failed_1" ,
921+ error : { code : "server_error" , message : "Upstream model unavailable" } ,
922+ } ,
923+ } ) ,
924+ ) ,
925+ ) ,
926+ )
927+
928+ expect ( response . events ) . toEqual ( [
929+ { type : "provider-error" , message : "server_error: Upstream model unavailable" } ,
930+ ] )
931+ } ) ,
932+ )
933+
934+ it . effect ( "surfaces response.failed code when no nested message is present" , ( ) =>
935+ Effect . gen ( function * ( ) {
936+ const response = yield * LLMClient . generate ( request ) . pipe (
937+ Effect . provide (
938+ fixedResponse (
939+ sseEvents ( {
940+ type : "response.failed" ,
941+ response : { id : "resp_failed_2" , error : { code : "invalid_prompt" } } ,
942+ } ) ,
943+ ) ,
944+ ) ,
945+ )
946+
947+ expect ( response . events ) . toEqual ( [ { type : "provider-error" , message : "invalid_prompt" } ] )
948+ } ) ,
949+ )
950+
951+ it . effect ( "surfaces error event details even when they arrive nested under response.error" , ( ) =>
952+ Effect . gen ( function * ( ) {
953+ // Some OpenAI-compatible proxies and older SDK versions wrap the
954+ // top-level error fields into a nested `response.error` payload
955+ // when they bubble up an HTTP error as an SSE `error` event. Honour
956+ // both shapes so the user still sees the underlying cause instead
957+ // of the catch-all string.
958+ const response = yield * LLMClient . generate ( request ) . pipe (
959+ Effect . provide (
960+ fixedResponse (
961+ sseEvents ( {
962+ type : "error" ,
963+ response : { error : { code : "context_length_exceeded" , message : "prompt too long" } } ,
964+ } ) ,
965+ ) ,
966+ ) ,
967+ )
968+
969+ expect ( response . events ) . toEqual ( [
970+ { type : "provider-error" , message : "context_length_exceeded: prompt too long" } ,
971+ ] )
972+ } ) ,
973+ )
974+
975+ it . effect ( "falls back to a stable default when both error and response are absent" , ( ) =>
976+ Effect . gen ( function * ( ) {
977+ const response = yield * LLMClient . generate ( request ) . pipe (
978+ Effect . provide ( fixedResponse ( sseEvents ( { type : "error" } ) ) ) ,
979+ )
980+
981+ expect ( response . events ) . toEqual ( [ { type : "provider-error" , message : "OpenAI Responses stream error" } ] )
982+ } ) ,
983+ )
984+
985+ it . effect ( "falls back to a stable default when response.failed has no error payload" , ( ) =>
986+ Effect . gen ( function * ( ) {
987+ const response = yield * LLMClient . generate ( request ) . pipe (
988+ Effect . provide ( fixedResponse ( sseEvents ( { type : "response.failed" , response : { id : "resp_failed_3" } } ) ) ) ,
989+ )
990+
991+ expect ( response . events ) . toEqual ( [ { type : "provider-error" , message : "OpenAI Responses response failed" } ] )
992+ } ) ,
993+ )
994+
894995 it . effect ( "fails HTTP provider errors before stream parsing" , ( ) =>
895996 Effect . gen ( function * ( ) {
896997 const error = yield * LLMClient . generate ( request ) . pipe (
0 commit comments