@ai-sdk/provider-utils 5.0.32 → 5.0.34

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ai-sdk/provider-utils",
3
- "version": "5.0.32",
3
+ "version": "5.0.34",
4
4
  "type": "module",
5
5
  "license": "Apache-2.0",
6
6
  "sideEffects": false,
@@ -32,7 +32,7 @@
32
32
  }
33
33
  },
34
34
  "dependencies": {
35
- "@ai-sdk/provider": "4.0.8",
35
+ "@ai-sdk/provider": "4.0.9",
36
36
  "@standard-schema/spec": "^1.1.0",
37
37
  "@workflow/serde": "4.1.0",
38
38
  "eventsource-parser": "^3.0.8",
@@ -3,7 +3,7 @@ import { isAbortError } from './is-abort-error';
3
3
 
4
4
  const FETCH_FAILED_ERROR_MESSAGES = ['fetch failed', 'failed to fetch'];
5
5
 
6
- const BUN_ERROR_CODES = [
6
+ const RETRYABLE_NETWORK_ERROR_CODES = new Set([
7
7
  'ConnectionRefused',
8
8
  'ConnectionClosed',
9
9
  'FailedToOpenSocket',
@@ -11,19 +11,33 @@ const BUN_ERROR_CODES = [
11
11
  'ECONNREFUSED',
12
12
  'ETIMEDOUT',
13
13
  'EPIPE',
14
- ];
14
+ 'UND_ERR_SOCKET',
15
+ 'UND_ERR_HEADERS_TIMEOUT',
16
+ 'UND_ERR_BODY_TIMEOUT',
17
+ 'UND_ERR_CONNECT_TIMEOUT',
18
+ ]);
15
19
 
16
- function isBunNetworkError(error: unknown): error is Error & { code?: string } {
17
- if (!(error instanceof Error)) {
18
- return false;
19
- }
20
+ function findNetworkError(
21
+ error: unknown,
22
+ ): (Error & { code?: unknown }) | undefined {
23
+ const visited = new Set<Error>();
24
+ let current = error;
25
+
26
+ while (current instanceof Error && !visited.has(current)) {
27
+ visited.add(current);
20
28
 
21
- const code = (error as any).code;
22
- if (typeof code === 'string' && BUN_ERROR_CODES.includes(code)) {
23
- return true;
29
+ const errorWithCode = current as Error & { code?: unknown };
30
+ if (
31
+ typeof errorWithCode.code === 'string' &&
32
+ RETRYABLE_NETWORK_ERROR_CODES.has(errorWithCode.code)
33
+ ) {
34
+ return errorWithCode;
35
+ }
36
+
37
+ current = (current as Error & { cause?: unknown }).cause;
24
38
  }
25
39
 
26
- return false;
40
+ return undefined;
27
41
  }
28
42
 
29
43
  export function handleFetchError({
@@ -58,9 +72,27 @@ export function handleFetchError({
58
72
  }
59
73
  }
60
74
 
61
- if (isBunNetworkError(error)) {
75
+ const networkError = findNetworkError(error);
76
+
77
+ if (networkError != null) {
78
+ if (APICallError.isInstance(error)) {
79
+ return new APICallError({
80
+ message: error.message,
81
+ cause: error.cause,
82
+ url: error.url,
83
+ requestBodyValues: error.requestBodyValues,
84
+ statusCode: error.statusCode,
85
+ responseHeaders: error.responseHeaders,
86
+ responseBody: error.responseBody,
87
+ data: error.data,
88
+ isRetryable: true,
89
+ });
90
+ }
91
+
62
92
  return new APICallError({
63
- message: `Cannot connect to API: ${error.message}`,
93
+ message: `Cannot connect to API: ${
94
+ error instanceof Error ? error.message : networkError.message
95
+ }`,
64
96
  cause: error,
65
97
  url,
66
98
  requestBodyValues,
@@ -1,5 +1,7 @@
1
1
  import { APICallError, EmptyResponseBodyError } from '@ai-sdk/provider';
2
2
  import { extractResponseHeaders } from './extract-response-headers';
3
+ import { handleFetchError } from './handle-fetch-error';
4
+ import { isAbortError } from './is-abort-error';
3
5
  import { parseJSON, safeParseJSON, type ParseResult } from './parse-json';
4
6
  import { parseJsonEventStream } from './parse-json-event-stream';
5
7
  import { readResponseWithSizeLimit } from './read-response-with-size-limit';
@@ -17,6 +19,74 @@ export type ResponseHandler<RETURN_TYPE> = (options: {
17
19
 
18
20
  const textDecoder = new TextDecoder();
19
21
 
22
+ function wrapResponseBodyStream({
23
+ stream,
24
+ url,
25
+ requestBodyValues,
26
+ statusCode,
27
+ responseHeaders,
28
+ }: {
29
+ stream: ReadableStream<Uint8Array>;
30
+ url: string;
31
+ requestBodyValues: unknown;
32
+ statusCode: number;
33
+ responseHeaders: Record<string, string>;
34
+ }): ReadableStream<Uint8Array> {
35
+ const reader = stream.getReader();
36
+ let readerReleased = false;
37
+
38
+ const releaseReader = () => {
39
+ if (!readerReleased) {
40
+ reader.releaseLock();
41
+ readerReleased = true;
42
+ }
43
+ };
44
+
45
+ return new ReadableStream<Uint8Array>({
46
+ async pull(controller) {
47
+ try {
48
+ const { done, value } = await reader.read();
49
+
50
+ if (done) {
51
+ releaseReader();
52
+ controller.close();
53
+ } else {
54
+ controller.enqueue(value);
55
+ }
56
+ } catch (error) {
57
+ releaseReader();
58
+
59
+ if (isAbortError(error)) {
60
+ controller.error(error);
61
+ return;
62
+ }
63
+
64
+ controller.error(
65
+ handleFetchError({
66
+ error: new APICallError({
67
+ message: 'Failed to process successful response',
68
+ cause: error,
69
+ statusCode,
70
+ url,
71
+ responseHeaders,
72
+ requestBodyValues,
73
+ }),
74
+ url,
75
+ requestBodyValues,
76
+ }),
77
+ );
78
+ }
79
+ },
80
+ async cancel(reason) {
81
+ try {
82
+ await reader.cancel(reason);
83
+ } finally {
84
+ releaseReader();
85
+ }
86
+ },
87
+ });
88
+ }
89
+
20
90
  async function readResponseBodyAsText({
21
91
  response,
22
92
  url,
@@ -102,7 +172,7 @@ export const createEventSourceResponseHandler =
102
172
  <T>(
103
173
  chunkSchema: FlexibleSchema<T>,
104
174
  ): ResponseHandler<ReadableStream<ParseResult<T>>> =>
105
- async ({ response }: { response: Response }) => {
175
+ async ({ response, url, requestBodyValues }) => {
106
176
  const responseHeaders = extractResponseHeaders(response);
107
177
 
108
178
  if (response.body == null) {
@@ -112,7 +182,13 @@ export const createEventSourceResponseHandler =
112
182
  return {
113
183
  responseHeaders,
114
184
  value: parseJsonEventStream({
115
- stream: response.body,
185
+ stream: wrapResponseBodyStream({
186
+ stream: response.body,
187
+ url,
188
+ requestBodyValues,
189
+ statusCode: response.status,
190
+ responseHeaders,
191
+ }),
116
192
  schema: chunkSchema,
117
193
  }),
118
194
  };