@ai-sdk/provider-utils 5.0.0-beta.4 → 5.0.0-beta.49

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.
Files changed (117) hide show
  1. package/CHANGELOG.md +373 -11
  2. package/dist/index.d.ts +1460 -553
  3. package/dist/index.js +1044 -361
  4. package/dist/index.js.map +1 -1
  5. package/dist/test/index.d.ts +2 -1
  6. package/dist/test/index.js +18 -37
  7. package/dist/test/index.js.map +1 -1
  8. package/package.json +16 -16
  9. package/src/add-additional-properties-to-json-schema.ts +1 -1
  10. package/src/as-array.ts +12 -0
  11. package/src/cancel-response-body.ts +19 -0
  12. package/src/convert-image-model-file-to-data-uri.ts +1 -1
  13. package/src/convert-inline-file-data-to-uint8-array.ts +30 -0
  14. package/src/create-tool-name-mapping.ts +1 -1
  15. package/src/detect-media-type.ts +312 -0
  16. package/src/download-blob.ts +8 -9
  17. package/src/extract-lines.ts +31 -0
  18. package/src/fetch-with-validated-redirects.ts +87 -0
  19. package/src/filter-nullable.ts +11 -0
  20. package/src/get-error-message.ts +1 -15
  21. package/src/get-from-api.ts +2 -2
  22. package/src/has-required-key.ts +6 -0
  23. package/src/index.ts +47 -12
  24. package/src/inject-json-instruction.ts +1 -1
  25. package/src/is-browser-runtime.ts +13 -0
  26. package/src/is-buffer.ts +9 -0
  27. package/src/is-json-serializable.ts +29 -0
  28. package/src/is-provider-reference.ts +21 -0
  29. package/src/is-same-origin.ts +19 -0
  30. package/src/is-url-supported.ts +17 -2
  31. package/src/load-api-key.ts +1 -1
  32. package/src/load-setting.ts +1 -1
  33. package/src/map-reasoning-to-provider.ts +108 -0
  34. package/src/maybe-promise-like.ts +3 -0
  35. package/src/parse-json-event-stream.ts +3 -3
  36. package/src/parse-json.ts +3 -3
  37. package/src/parse-provider-options.ts +1 -1
  38. package/src/post-to-api.ts +4 -4
  39. package/src/provider-defined-tool-factory.ts +129 -0
  40. package/src/provider-executed-tool-factory.ts +69 -0
  41. package/src/read-response-with-size-limit.ts +4 -0
  42. package/src/resolve-full-media-type.ts +49 -0
  43. package/src/resolve-provider-reference.ts +26 -0
  44. package/src/resolve.ts +16 -1
  45. package/src/response-handler.ts +3 -3
  46. package/src/schema.ts +11 -4
  47. package/src/secure-json-parse.ts +1 -1
  48. package/src/serialize-model-options.ts +63 -0
  49. package/src/streaming-tool-call-tracker.ts +241 -0
  50. package/src/test/convert-response-stream-to-array.ts +1 -1
  51. package/src/test/is-node-version.ts +22 -1
  52. package/src/to-json-schema/zod3-to-json-schema/options.ts +3 -3
  53. package/src/to-json-schema/zod3-to-json-schema/parse-def.ts +3 -3
  54. package/src/to-json-schema/zod3-to-json-schema/parse-types.ts +22 -22
  55. package/src/to-json-schema/zod3-to-json-schema/parsers/array.ts +3 -3
  56. package/src/to-json-schema/zod3-to-json-schema/parsers/bigint.ts +1 -1
  57. package/src/to-json-schema/zod3-to-json-schema/parsers/branded.ts +2 -2
  58. package/src/to-json-schema/zod3-to-json-schema/parsers/catch.ts +2 -2
  59. package/src/to-json-schema/zod3-to-json-schema/parsers/date.ts +4 -4
  60. package/src/to-json-schema/zod3-to-json-schema/parsers/default.ts +3 -3
  61. package/src/to-json-schema/zod3-to-json-schema/parsers/effects.ts +3 -3
  62. package/src/to-json-schema/zod3-to-json-schema/parsers/enum.ts +1 -1
  63. package/src/to-json-schema/zod3-to-json-schema/parsers/intersection.ts +5 -5
  64. package/src/to-json-schema/zod3-to-json-schema/parsers/literal.ts +1 -1
  65. package/src/to-json-schema/zod3-to-json-schema/parsers/map.ts +4 -5
  66. package/src/to-json-schema/zod3-to-json-schema/parsers/native-enum.ts +1 -1
  67. package/src/to-json-schema/zod3-to-json-schema/parsers/never.ts +1 -2
  68. package/src/to-json-schema/zod3-to-json-schema/parsers/nullable.ts +4 -4
  69. package/src/to-json-schema/zod3-to-json-schema/parsers/number.ts +1 -1
  70. package/src/to-json-schema/zod3-to-json-schema/parsers/object.ts +3 -3
  71. package/src/to-json-schema/zod3-to-json-schema/parsers/optional.ts +3 -3
  72. package/src/to-json-schema/zod3-to-json-schema/parsers/pipeline.ts +10 -8
  73. package/src/to-json-schema/zod3-to-json-schema/parsers/promise.ts +3 -3
  74. package/src/to-json-schema/zod3-to-json-schema/parsers/readonly.ts +2 -2
  75. package/src/to-json-schema/zod3-to-json-schema/parsers/record.ts +9 -10
  76. package/src/to-json-schema/zod3-to-json-schema/parsers/set.ts +3 -3
  77. package/src/to-json-schema/zod3-to-json-schema/parsers/string.ts +2 -2
  78. package/src/to-json-schema/zod3-to-json-schema/parsers/tuple.ts +3 -3
  79. package/src/to-json-schema/zod3-to-json-schema/parsers/undefined.ts +1 -2
  80. package/src/to-json-schema/zod3-to-json-schema/parsers/union.ts +3 -3
  81. package/src/to-json-schema/zod3-to-json-schema/parsers/unknown.ts +1 -2
  82. package/src/to-json-schema/zod3-to-json-schema/refs.ts +3 -3
  83. package/src/to-json-schema/zod3-to-json-schema/select-parser.ts +2 -2
  84. package/src/to-json-schema/zod3-to-json-schema/zod3-to-json-schema.ts +3 -3
  85. package/src/types/assistant-model-message.ts +5 -3
  86. package/src/types/content-part.ts +158 -19
  87. package/src/types/context.ts +4 -0
  88. package/src/types/executable-tool.ts +17 -0
  89. package/src/types/execute-tool.ts +29 -9
  90. package/src/types/file-data.ts +48 -0
  91. package/src/types/index.ts +29 -11
  92. package/src/types/infer-tool-context.ts +41 -0
  93. package/src/types/infer-tool-input.ts +7 -0
  94. package/src/types/infer-tool-output.ts +7 -0
  95. package/src/types/infer-tool-set-context.ts +44 -0
  96. package/src/types/model-message.ts +4 -4
  97. package/src/types/never-optional.ts +7 -0
  98. package/src/types/provider-options.ts +1 -1
  99. package/src/types/provider-reference.ts +10 -0
  100. package/src/types/sandbox.ts +217 -0
  101. package/src/types/system-model-message.ts +1 -1
  102. package/src/types/tool-approval-request.ts +13 -0
  103. package/src/types/tool-execute-function.ts +56 -0
  104. package/src/types/tool-model-message.ts +3 -3
  105. package/src/types/tool-needs-approval-function.ts +39 -0
  106. package/src/types/tool-set.ts +22 -0
  107. package/src/types/tool.ts +278 -225
  108. package/src/types/user-model-message.ts +2 -2
  109. package/src/validate-download-url.ts +120 -33
  110. package/src/validate-types.ts +5 -3
  111. package/dist/index.d.mts +0 -1455
  112. package/dist/index.mjs +0 -2754
  113. package/dist/index.mjs.map +0 -1
  114. package/dist/test/index.d.mts +0 -17
  115. package/dist/test/index.mjs +0 -77
  116. package/dist/test/index.mjs.map +0 -1
  117. package/src/provider-tool-factory.ts +0 -125
@@ -4,6 +4,10 @@ import { DownloadError } from './download-error';
4
4
  * Validates that a URL is safe to download from, blocking private/internal addresses
5
5
  * to prevent SSRF attacks.
6
6
  *
7
+ * Note: this performs string/literal-IP checks only. It does not resolve DNS, so a
8
+ * hostname that resolves to a private address is not blocked here (see callers, which
9
+ * should additionally constrain egress at the network layer when handling untrusted URLs).
10
+ *
7
11
  * @param url - The URL string to validate.
8
12
  * @throws DownloadError if the URL is unsafe.
9
13
  */
@@ -18,15 +22,22 @@ export function validateDownloadUrl(url: string): void {
18
22
  });
19
23
  }
20
24
 
21
- // Only allow http and https protocols
25
+ // data: URLs are inline content, so they do not trigger a network fetch or SSRF risk.
26
+ if (parsed.protocol === 'data:') {
27
+ return;
28
+ }
29
+
30
+ // Only allow http and https network protocols
22
31
  if (parsed.protocol !== 'http:' && parsed.protocol !== 'https:') {
23
32
  throw new DownloadError({
24
33
  url,
25
- message: `URL scheme must be http or https, got ${parsed.protocol}`,
34
+ message: `URL scheme must be http, https, or data, got ${parsed.protocol}`,
26
35
  });
27
36
  }
28
37
 
29
- const hostname = parsed.hostname;
38
+ // Strip a trailing dot so a fully-qualified name like `localhost.` (which resolves
39
+ // identically to `localhost`) cannot bypass the hostname blocklist below.
40
+ const hostname = parsed.hostname.toLowerCase().replace(/\.+$/, '');
30
41
 
31
42
  // Block empty hostname
32
43
  if (!hostname) {
@@ -85,59 +96,135 @@ function isIPv4(hostname: string): boolean {
85
96
 
86
97
  function isPrivateIPv4(ip: string): boolean {
87
98
  const parts = ip.split('.').map(Number);
88
- const [a, b] = parts;
99
+ const [a, b, c] = parts;
89
100
 
90
101
  // 0.0.0.0/8
91
102
  if (a === 0) return true;
92
103
  // 10.0.0.0/8
93
104
  if (a === 10) return true;
105
+ // 100.64.0.0/10 (CGNAT, used by some cloud providers for internal traffic)
106
+ if (a === 100 && b >= 64 && b <= 127) return true;
94
107
  // 127.0.0.0/8
95
108
  if (a === 127) return true;
96
109
  // 169.254.0.0/16
97
110
  if (a === 169 && b === 254) return true;
98
111
  // 172.16.0.0/12
99
112
  if (a === 172 && b >= 16 && b <= 31) return true;
113
+ // 192.0.0.0/24 (IETF protocol assignments)
114
+ if (a === 192 && b === 0 && c === 0) return true;
100
115
  // 192.168.0.0/16
101
116
  if (a === 192 && b === 168) return true;
117
+ // 198.18.0.0/15 (benchmarking)
118
+ if (a === 198 && (b === 18 || b === 19)) return true;
119
+ // 240.0.0.0/4 (reserved, includes 255.255.255.255 broadcast)
120
+ if (a >= 240) return true;
102
121
 
103
122
  return false;
104
123
  }
105
124
 
106
- function isPrivateIPv6(ip: string): boolean {
107
- const normalized = ip.toLowerCase();
108
-
109
- // ::1 (loopback)
110
- if (normalized === '::1') return true;
111
- // :: (unspecified)
112
- if (normalized === '::') return true;
113
-
114
- // Check for IPv4-mapped addresses (::ffff:x.x.x.x or ::ffff:HHHH:HHHH)
115
- if (normalized.startsWith('::ffff:')) {
116
- const mappedPart = normalized.slice(7);
117
- // Dotted-decimal form: ::ffff:127.0.0.1
118
- if (isIPv4(mappedPart)) {
119
- return isPrivateIPv4(mappedPart);
120
- }
121
- // Hex form: ::ffff:7f00:1 (URL parser normalizes to this)
122
- const hexParts = mappedPart.split(':');
123
- if (hexParts.length === 2) {
124
- const high = parseInt(hexParts[0], 16);
125
- const low = parseInt(hexParts[1], 16);
126
- if (!isNaN(high) && !isNaN(low)) {
127
- const a = (high >> 8) & 0xff;
128
- const b = high & 0xff;
129
- const c = (low >> 8) & 0xff;
130
- const d = low & 0xff;
131
- return isPrivateIPv4(`${a}.${b}.${c}.${d}`);
125
+ /**
126
+ * Expands an IPv6 address string into its 8 16-bit groups, handling `::`
127
+ * compression and an optional dotted-decimal IPv4 tail (e.g. `::ffff:127.0.0.1`).
128
+ *
129
+ * @returns the 8 groups, or null if the input is not a parseable IPv6 address.
130
+ */
131
+ function parseIPv6(ip: string): number[] | null {
132
+ // Strip an optional zone id (e.g. `fe80::1%eth0`).
133
+ let address = ip.toLowerCase();
134
+ const zoneIndex = address.indexOf('%');
135
+ if (zoneIndex !== -1) {
136
+ address = address.slice(0, zoneIndex);
137
+ }
138
+
139
+ // At most one `::` compression marker is allowed.
140
+ const halves = address.split('::');
141
+ if (halves.length > 2) return null;
142
+
143
+ const toGroups = (segment: string): number[] | null => {
144
+ if (segment === '') return [];
145
+ const groups: number[] = [];
146
+ const parts = segment.split(':');
147
+ for (let i = 0; i < parts.length; i++) {
148
+ const part = parts[i];
149
+ // A dotted-decimal IPv4 tail is only valid as the final part.
150
+ if (part.includes('.')) {
151
+ if (i !== parts.length - 1 || !isIPv4(part)) return null;
152
+ const [a, b, c, d] = part.split('.').map(Number);
153
+ groups.push((a << 8) | b, (c << 8) | d);
154
+ continue;
132
155
  }
156
+ if (!/^[0-9a-f]{1,4}$/.test(part)) return null;
157
+ groups.push(parseInt(part, 16));
133
158
  }
159
+ return groups;
160
+ };
161
+
162
+ const head = toGroups(halves[0]);
163
+ if (head === null) return null;
164
+
165
+ if (halves.length === 2) {
166
+ const tail = toGroups(halves[1]);
167
+ if (tail === null) return null;
168
+ const fill = 8 - head.length - tail.length;
169
+ if (fill < 0) return null;
170
+ return [...head, ...new Array<number>(fill).fill(0), ...tail];
134
171
  }
135
172
 
136
- // fc00::/7 (unique local addresses - fc00:: and fd00::)
137
- if (normalized.startsWith('fc') || normalized.startsWith('fd')) return true;
173
+ // No `::` compression: the address must contain exactly 8 groups.
174
+ return head.length === 8 ? head : null;
175
+ }
176
+
177
+ function isPrivateIPv6(ip: string): boolean {
178
+ const groups = parseIPv6(ip);
179
+
180
+ // Fail closed: if the address cannot be parsed, treat it as unsafe.
181
+ if (groups === null) return true;
182
+
183
+ const topZero = (count: number) =>
184
+ groups.slice(0, count).every(group => group === 0);
185
+
186
+ // ::1 (loopback) and :: (unspecified)
187
+ if (topZero(7) && (groups[7] === 0 || groups[7] === 1)) return true;
188
+
189
+ // fc00::/7 (unique local addresses)
190
+ if ((groups[0] & 0xfe00) === 0xfc00) return true;
138
191
 
139
192
  // fe80::/10 (link-local)
140
- if (normalized.startsWith('fe80')) return true;
193
+ if ((groups[0] & 0xffc0) === 0xfe80) return true;
194
+
195
+ // fec0::/10 (site-local, deprecated but still routable internally)
196
+ if ((groups[0] & 0xffc0) === 0xfec0) return true;
197
+
198
+ // ff00::/8 (multicast)
199
+ if ((groups[0] & 0xff00) === 0xff00) return true;
200
+
201
+ // Addresses that embed an IPv4 address in their last 32 bits. For these we
202
+ // extract the embedded IPv4 and reuse the IPv4 private-range checks, so that
203
+ // e.g. ::ffff:127.0.0.1 or 64:ff9b::169.254.169.254 are blocked.
204
+ const embedsIPv4 =
205
+ // ::/96 — IPv4-compatible (deprecated)
206
+ topZero(6) ||
207
+ // ::ffff:0:0/96 — IPv4-mapped (ffff in group 5)
208
+ (topZero(5) && groups[5] === 0xffff) ||
209
+ // ::ffff:0:0/96 — IPv4-translated form (ffff in group 4, group 5 zero)
210
+ (topZero(4) && groups[4] === 0xffff && groups[5] === 0) ||
211
+ // 64:ff9b::/96 — NAT64 well-known prefix
212
+ (groups[0] === 0x0064 &&
213
+ groups[1] === 0xff9b &&
214
+ groups[2] === 0 &&
215
+ groups[3] === 0 &&
216
+ groups[4] === 0 &&
217
+ groups[5] === 0) ||
218
+ // 64:ff9b:1::/48 — NAT64 local-use prefix
219
+ (groups[0] === 0x0064 && groups[1] === 0xff9b && groups[2] === 0x0001);
220
+
221
+ if (embedsIPv4) {
222
+ const a = (groups[6] >> 8) & 0xff;
223
+ const b = groups[6] & 0xff;
224
+ const c = (groups[7] >> 8) & 0xff;
225
+ const d = groups[7] & 0xff;
226
+ return isPrivateIPv4(`${a}.${b}.${c}.${d}`);
227
+ }
141
228
 
142
229
  return false;
143
230
  }
@@ -1,6 +1,8 @@
1
- import { TypeValidationContext, TypeValidationError } from '@ai-sdk/provider';
2
- import { FlexibleSchema, asSchema } from './schema';
3
-
1
+ import {
2
+ TypeValidationError,
3
+ type TypeValidationContext,
4
+ } from '@ai-sdk/provider';
5
+ import { asSchema, type FlexibleSchema } from './schema';
4
6
  /**
5
7
  * Validates the types of an unknown object using a schema and
6
8
  * return a strongly-typed object.