1
- import { APICallError , NoResponseBodyError } from '@ai-sdk/provider' ;
1
+ import { APICallError , EmptyResponseBodyError } from '@ai-sdk/provider' ;
2
2
import {
3
3
EventSourceParserStream ,
4
4
ParsedEvent ,
5
5
} from 'eventsource-parser/stream' ;
6
6
import { ZodSchema } from 'zod' ;
7
+ import { extractResponseHeaders } from './extract-response-headers' ;
7
8
import { ParseResult , parseJSON , safeParseJSON } from './parse-json' ;
8
9
9
10
export type ResponseHandler < RETURN_TYPE > = ( options : {
10
11
url : string ;
11
12
requestBodyValues : unknown ;
12
13
response : Response ;
13
- } ) => PromiseLike < RETURN_TYPE > ;
14
+ } ) => PromiseLike < {
15
+ value : RETURN_TYPE ;
16
+ responseHeaders ?: Record < string , string > ;
17
+ } > ;
14
18
15
19
export const createJsonErrorResponseHandler =
16
20
< T > ( {
@@ -24,17 +28,22 @@ export const createJsonErrorResponseHandler =
24
28
} ) : ResponseHandler < APICallError > =>
25
29
async ( { response, url, requestBodyValues } ) => {
26
30
const responseBody = await response . text ( ) ;
31
+ const responseHeaders = extractResponseHeaders ( response ) ;
27
32
28
33
// Some providers return an empty response body for some errors:
29
34
if ( responseBody . trim ( ) === '' ) {
30
- return new APICallError ( {
31
- message : response . statusText ,
32
- url,
33
- requestBodyValues,
34
- statusCode : response . status ,
35
- responseBody,
36
- isRetryable : isRetryable ?.( response ) ,
37
- } ) ;
35
+ return {
36
+ responseHeaders,
37
+ value : new APICallError ( {
38
+ message : response . statusText ,
39
+ url,
40
+ requestBodyValues,
41
+ statusCode : response . status ,
42
+ responseHeaders,
43
+ responseBody,
44
+ isRetryable : isRetryable ?.( response ) ,
45
+ } ) ,
46
+ } ;
38
47
}
39
48
40
49
// resilient parsing in case the response is not JSON or does not match the schema:
@@ -44,24 +53,32 @@ export const createJsonErrorResponseHandler =
44
53
schema : errorSchema ,
45
54
} ) ;
46
55
47
- return new APICallError ( {
48
- message : errorToMessage ( parsedError ) ,
49
- url,
50
- requestBodyValues,
51
- statusCode : response . status ,
52
- responseBody,
53
- data : parsedError ,
54
- isRetryable : isRetryable ?.( response , parsedError ) ,
55
- } ) ;
56
+ return {
57
+ responseHeaders,
58
+ value : new APICallError ( {
59
+ message : errorToMessage ( parsedError ) ,
60
+ url,
61
+ requestBodyValues,
62
+ statusCode : response . status ,
63
+ responseHeaders,
64
+ responseBody,
65
+ data : parsedError ,
66
+ isRetryable : isRetryable ?.( response , parsedError ) ,
67
+ } ) ,
68
+ } ;
56
69
} catch ( parseError ) {
57
- return new APICallError ( {
58
- message : response . statusText ,
59
- url,
60
- requestBodyValues,
61
- statusCode : response . status ,
62
- responseBody,
63
- isRetryable : isRetryable ?.( response ) ,
64
- } ) ;
70
+ return {
71
+ responseHeaders,
72
+ value : new APICallError ( {
73
+ message : response . statusText ,
74
+ url,
75
+ requestBodyValues,
76
+ statusCode : response . status ,
77
+ responseHeaders,
78
+ responseBody,
79
+ isRetryable : isRetryable ?.( response ) ,
80
+ } ) ,
81
+ } ;
65
82
}
66
83
} ;
67
84
@@ -70,30 +87,35 @@ export const createEventSourceResponseHandler =
70
87
chunkSchema : ZodSchema < T > ,
71
88
) : ResponseHandler < ReadableStream < ParseResult < T > > > =>
72
89
async ( { response } : { response : Response } ) => {
90
+ const responseHeaders = extractResponseHeaders ( response ) ;
91
+
73
92
if ( response . body == null ) {
74
- throw new NoResponseBodyError ( ) ;
93
+ throw new EmptyResponseBodyError ( { } ) ;
75
94
}
76
95
77
- return response . body
78
- . pipeThrough ( new TextDecoderStream ( ) )
79
- . pipeThrough ( new EventSourceParserStream ( ) )
80
- . pipeThrough (
81
- new TransformStream < ParsedEvent , ParseResult < T > > ( {
82
- transform ( { data } , controller ) {
83
- // ignore the 'DONE' event that e.g. OpenAI sends:
84
- if ( data === '[DONE]' ) {
85
- return ;
86
- }
96
+ return {
97
+ responseHeaders,
98
+ value : response . body
99
+ . pipeThrough ( new TextDecoderStream ( ) )
100
+ . pipeThrough ( new EventSourceParserStream ( ) )
101
+ . pipeThrough (
102
+ new TransformStream < ParsedEvent , ParseResult < T > > ( {
103
+ transform ( { data } , controller ) {
104
+ // ignore the 'DONE' event that e.g. OpenAI sends:
105
+ if ( data === '[DONE]' ) {
106
+ return ;
107
+ }
87
108
88
- controller . enqueue (
89
- safeParseJSON ( {
90
- text : data ,
91
- schema : chunkSchema ,
92
- } ) ,
93
- ) ;
94
- } ,
95
- } ) ,
96
- ) ;
109
+ controller . enqueue (
110
+ safeParseJSON ( {
111
+ text : data ,
112
+ schema : chunkSchema ,
113
+ } ) ,
114
+ ) ;
115
+ } ,
116
+ } ) ,
117
+ ) ,
118
+ } ;
97
119
} ;
98
120
99
121
export const createJsonResponseHandler =
@@ -106,16 +128,22 @@ export const createJsonResponseHandler =
106
128
schema : responseSchema ,
107
129
} ) ;
108
130
131
+ const responseHeaders = extractResponseHeaders ( response ) ;
132
+
109
133
if ( ! parsedResult . success ) {
110
134
throw new APICallError ( {
111
135
message : 'Invalid JSON response' ,
112
136
cause : parsedResult . error ,
113
137
statusCode : response . status ,
138
+ responseHeaders,
114
139
responseBody,
115
140
url,
116
141
requestBodyValues,
117
142
} ) ;
118
143
}
119
144
120
- return parsedResult . value ;
145
+ return {
146
+ responseHeaders,
147
+ value : parsedResult . value ,
148
+ } ;
121
149
} ;
0 commit comments