@ai-sdk/provider-utils 4.0.4 → 4.0.6
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/CHANGELOG.md +14 -0
- package/dist/index.js +6 -4
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +6 -4
- package/dist/index.mjs.map +1 -1
- package/package.json +4 -2
- package/src/__snapshots__/schema.test.ts.snap +346 -0
- package/src/add-additional-properties-to-json-schema.test.ts +289 -0
- package/src/add-additional-properties-to-json-schema.ts +53 -0
- package/src/combine-headers.ts +11 -0
- package/src/convert-async-iterator-to-readable-stream.test.ts +78 -0
- package/src/convert-async-iterator-to-readable-stream.ts +47 -0
- package/src/convert-image-model-file-to-data-uri.test.ts +85 -0
- package/src/convert-image-model-file-to-data-uri.ts +19 -0
- package/src/convert-to-form-data.test.ts +167 -0
- package/src/convert-to-form-data.ts +61 -0
- package/src/create-tool-name-mapping.test.ts +163 -0
- package/src/create-tool-name-mapping.ts +66 -0
- package/src/delay.test.ts +212 -0
- package/src/delay.ts +47 -0
- package/src/delayed-promise.test.ts +132 -0
- package/src/delayed-promise.ts +61 -0
- package/src/download-blob.test.ts +145 -0
- package/src/download-blob.ts +31 -0
- package/src/download-error.ts +39 -0
- package/src/extract-response-headers.ts +9 -0
- package/src/fetch-function.ts +4 -0
- package/src/generate-id.test.ts +31 -0
- package/src/generate-id.ts +57 -0
- package/src/get-error-message.ts +15 -0
- package/src/get-from-api.test.ts +199 -0
- package/src/get-from-api.ts +97 -0
- package/src/get-runtime-environment-user-agent.test.ts +47 -0
- package/src/get-runtime-environment-user-agent.ts +24 -0
- package/src/handle-fetch-error.ts +39 -0
- package/src/index.ts +67 -0
- package/src/inject-json-instruction.test.ts +404 -0
- package/src/inject-json-instruction.ts +63 -0
- package/src/is-abort-error.ts +8 -0
- package/src/is-async-iterable.ts +3 -0
- package/src/is-non-nullable.ts +12 -0
- package/src/is-url-supported.test.ts +282 -0
- package/src/is-url-supported.ts +40 -0
- package/src/load-api-key.ts +45 -0
- package/src/load-optional-setting.ts +30 -0
- package/src/load-setting.ts +62 -0
- package/src/maybe-promise-like.ts +3 -0
- package/src/media-type-to-extension.test.ts +26 -0
- package/src/media-type-to-extension.ts +22 -0
- package/src/normalize-headers.test.ts +64 -0
- package/src/normalize-headers.ts +38 -0
- package/src/parse-json-event-stream.ts +33 -0
- package/src/parse-json.test.ts +191 -0
- package/src/parse-json.ts +122 -0
- package/src/parse-provider-options.ts +32 -0
- package/src/post-to-api.ts +166 -0
- package/src/provider-tool-factory.ts +125 -0
- package/src/remove-undefined-entries.test.ts +57 -0
- package/src/remove-undefined-entries.ts +12 -0
- package/src/resolve.test.ts +125 -0
- package/src/resolve.ts +17 -0
- package/src/response-handler.test.ts +89 -0
- package/src/response-handler.ts +187 -0
- package/src/schema.test-d.ts +11 -0
- package/src/schema.test.ts +502 -0
- package/src/schema.ts +267 -0
- package/src/secure-json-parse.test.ts +59 -0
- package/src/secure-json-parse.ts +92 -0
- package/src/test/convert-array-to-async-iterable.ts +9 -0
- package/src/test/convert-array-to-readable-stream.ts +15 -0
- package/src/test/convert-async-iterable-to-array.ts +9 -0
- package/src/test/convert-readable-stream-to-array.ts +14 -0
- package/src/test/convert-response-stream-to-array.ts +9 -0
- package/src/test/index.ts +7 -0
- package/src/test/is-node-version.ts +4 -0
- package/src/test/mock-id.ts +8 -0
- package/src/to-json-schema/zod3-to-json-schema/LICENSE +16 -0
- package/src/to-json-schema/zod3-to-json-schema/README.md +24 -0
- package/src/to-json-schema/zod3-to-json-schema/get-relative-path.ts +7 -0
- package/src/to-json-schema/zod3-to-json-schema/index.ts +1 -0
- package/src/to-json-schema/zod3-to-json-schema/options.ts +98 -0
- package/src/to-json-schema/zod3-to-json-schema/parse-def.test.ts +224 -0
- package/src/to-json-schema/zod3-to-json-schema/parse-def.ts +109 -0
- package/src/to-json-schema/zod3-to-json-schema/parse-types.ts +57 -0
- package/src/to-json-schema/zod3-to-json-schema/parsers/any.ts +5 -0
- package/src/to-json-schema/zod3-to-json-schema/parsers/array.test.ts +98 -0
- package/src/to-json-schema/zod3-to-json-schema/parsers/array.ts +38 -0
- package/src/to-json-schema/zod3-to-json-schema/parsers/bigint.test.ts +51 -0
- package/src/to-json-schema/zod3-to-json-schema/parsers/bigint.ts +44 -0
- package/src/to-json-schema/zod3-to-json-schema/parsers/boolean.ts +7 -0
- package/src/to-json-schema/zod3-to-json-schema/parsers/branded.test.ts +16 -0
- package/src/to-json-schema/zod3-to-json-schema/parsers/branded.ts +7 -0
- package/src/to-json-schema/zod3-to-json-schema/parsers/catch.test.ts +15 -0
- package/src/to-json-schema/zod3-to-json-schema/parsers/catch.ts +7 -0
- package/src/to-json-schema/zod3-to-json-schema/parsers/date.test.ts +97 -0
- package/src/to-json-schema/zod3-to-json-schema/parsers/date.ts +64 -0
- package/src/to-json-schema/zod3-to-json-schema/parsers/default.test.ts +54 -0
- package/src/to-json-schema/zod3-to-json-schema/parsers/default.ts +14 -0
- package/src/to-json-schema/zod3-to-json-schema/parsers/effects.test.ts +41 -0
- package/src/to-json-schema/zod3-to-json-schema/parsers/effects.ts +14 -0
- package/src/to-json-schema/zod3-to-json-schema/parsers/enum.ts +13 -0
- package/src/to-json-schema/zod3-to-json-schema/parsers/intersection.test.ts +92 -0
- package/src/to-json-schema/zod3-to-json-schema/parsers/intersection.ts +52 -0
- package/src/to-json-schema/zod3-to-json-schema/parsers/literal.ts +29 -0
- package/src/to-json-schema/zod3-to-json-schema/parsers/map.test.ts +48 -0
- package/src/to-json-schema/zod3-to-json-schema/parsers/map.ts +47 -0
- package/src/to-json-schema/zod3-to-json-schema/parsers/native-enum.test.ts +102 -0
- package/src/to-json-schema/zod3-to-json-schema/parsers/native-enum.ts +31 -0
- package/src/to-json-schema/zod3-to-json-schema/parsers/never.ts +9 -0
- package/src/to-json-schema/zod3-to-json-schema/parsers/null.ts +9 -0
- package/src/to-json-schema/zod3-to-json-schema/parsers/nullable.test.ts +67 -0
- package/src/to-json-schema/zod3-to-json-schema/parsers/nullable.ts +42 -0
- package/src/to-json-schema/zod3-to-json-schema/parsers/number.test.ts +65 -0
- package/src/to-json-schema/zod3-to-json-schema/parsers/number.ts +44 -0
- package/src/to-json-schema/zod3-to-json-schema/parsers/object.test.ts +149 -0
- package/src/to-json-schema/zod3-to-json-schema/parsers/object.ts +88 -0
- package/src/to-json-schema/zod3-to-json-schema/parsers/optional.test.ts +147 -0
- package/src/to-json-schema/zod3-to-json-schema/parsers/optional.ts +23 -0
- package/src/to-json-schema/zod3-to-json-schema/parsers/pipe.test.ts +35 -0
- package/src/to-json-schema/zod3-to-json-schema/parsers/pipeline.ts +29 -0
- package/src/to-json-schema/zod3-to-json-schema/parsers/promise.test.ts +15 -0
- package/src/to-json-schema/zod3-to-json-schema/parsers/promise.ts +11 -0
- package/src/to-json-schema/zod3-to-json-schema/parsers/readonly.test.ts +20 -0
- package/src/to-json-schema/zod3-to-json-schema/parsers/readonly.ts +7 -0
- package/src/to-json-schema/zod3-to-json-schema/parsers/record.test.ts +108 -0
- package/src/to-json-schema/zod3-to-json-schema/parsers/record.ts +71 -0
- package/src/to-json-schema/zod3-to-json-schema/parsers/set.test.ts +20 -0
- package/src/to-json-schema/zod3-to-json-schema/parsers/set.ts +35 -0
- package/src/to-json-schema/zod3-to-json-schema/parsers/string.test.ts +438 -0
- package/src/to-json-schema/zod3-to-json-schema/parsers/string.ts +426 -0
- package/src/to-json-schema/zod3-to-json-schema/parsers/tuple.test.ts +33 -0
- package/src/to-json-schema/zod3-to-json-schema/parsers/tuple.ts +61 -0
- package/src/to-json-schema/zod3-to-json-schema/parsers/undefined.ts +11 -0
- package/src/to-json-schema/zod3-to-json-schema/parsers/union.test.ts +226 -0
- package/src/to-json-schema/zod3-to-json-schema/parsers/union.ts +144 -0
- package/src/to-json-schema/zod3-to-json-schema/parsers/unknown.ts +7 -0
- package/src/to-json-schema/zod3-to-json-schema/refs.test.ts +919 -0
- package/src/to-json-schema/zod3-to-json-schema/refs.ts +39 -0
- package/src/to-json-schema/zod3-to-json-schema/select-parser.ts +115 -0
- package/src/to-json-schema/zod3-to-json-schema/zod3-to-json-schema.test.ts +862 -0
- package/src/to-json-schema/zod3-to-json-schema/zod3-to-json-schema.ts +93 -0
- package/src/types/assistant-model-message.ts +39 -0
- package/src/types/content-part.ts +379 -0
- package/src/types/data-content.ts +4 -0
- package/src/types/execute-tool.ts +27 -0
- package/src/types/index.ts +40 -0
- package/src/types/model-message.ts +14 -0
- package/src/types/provider-options.ts +9 -0
- package/src/types/system-model-message.ts +20 -0
- package/src/types/tool-approval-request.ts +16 -0
- package/src/types/tool-approval-response.ts +27 -0
- package/src/types/tool-call.ts +31 -0
- package/src/types/tool-model-message.ts +23 -0
- package/src/types/tool-result.ts +35 -0
- package/src/types/tool.test-d.ts +193 -0
- package/src/types/tool.ts +324 -0
- package/src/types/user-model-message.ts +22 -0
- package/src/uint8-utils.ts +26 -0
- package/src/validate-types.test.ts +105 -0
- package/src/validate-types.ts +81 -0
- package/src/version.ts +6 -0
- package/src/with-user-agent-suffix.test.ts +84 -0
- package/src/with-user-agent-suffix.ts +27 -0
- package/src/without-trailing-slash.ts +3 -0
- package/LICENSE +0 -13
|
@@ -0,0 +1,282 @@
|
|
|
1
|
+
import { isUrlSupported } from './is-url-supported';
|
|
2
|
+
import { describe, expect, it } from 'vitest';
|
|
3
|
+
|
|
4
|
+
describe('isUrlSupported', () => {
|
|
5
|
+
describe('when the model does not support any URLs', () => {
|
|
6
|
+
it('should return false', async () => {
|
|
7
|
+
expect(
|
|
8
|
+
isUrlSupported({
|
|
9
|
+
mediaType: 'text/plain',
|
|
10
|
+
url: 'https://example.com',
|
|
11
|
+
supportedUrls: {},
|
|
12
|
+
}),
|
|
13
|
+
).toBe(false);
|
|
14
|
+
});
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
describe('when the model supports specific media types and URLs', () => {
|
|
18
|
+
it('should return true for exact media type and exact URL match', async () => {
|
|
19
|
+
expect(
|
|
20
|
+
isUrlSupported({
|
|
21
|
+
mediaType: 'text/plain',
|
|
22
|
+
url: 'https://example.com',
|
|
23
|
+
supportedUrls: { 'text/plain': [/https:\/\/example\.com/] },
|
|
24
|
+
}),
|
|
25
|
+
).toBe(true);
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it('should return true for exact media type and regex URL match', async () => {
|
|
29
|
+
expect(
|
|
30
|
+
isUrlSupported({
|
|
31
|
+
mediaType: 'image/png',
|
|
32
|
+
url: 'https://images.example.com/cat.png',
|
|
33
|
+
supportedUrls: {
|
|
34
|
+
'image/png': [/https:\/\/images\.example\.com\/.+/],
|
|
35
|
+
},
|
|
36
|
+
}),
|
|
37
|
+
).toBe(true);
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it('should return true for exact media type and one of multiple regex URLs match', async () => {
|
|
41
|
+
expect(
|
|
42
|
+
isUrlSupported({
|
|
43
|
+
mediaType: 'image/png',
|
|
44
|
+
url: 'https://another.com/img.png',
|
|
45
|
+
supportedUrls: {
|
|
46
|
+
'image/png': [
|
|
47
|
+
/https:\/\/images\.example\.com\/.+/,
|
|
48
|
+
/https:\/\/another\.com\/img\.png/,
|
|
49
|
+
],
|
|
50
|
+
},
|
|
51
|
+
}),
|
|
52
|
+
).toBe(true);
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it('should return false for exact media type but URL mismatch', async () => {
|
|
56
|
+
expect(
|
|
57
|
+
isUrlSupported({
|
|
58
|
+
mediaType: 'text/plain',
|
|
59
|
+
url: 'https://another.com',
|
|
60
|
+
supportedUrls: { 'text/plain': [/https:\/\/example\.com/] },
|
|
61
|
+
}),
|
|
62
|
+
).toBe(false);
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it('should return false for URL match but media type mismatch', async () => {
|
|
66
|
+
expect(
|
|
67
|
+
isUrlSupported({
|
|
68
|
+
mediaType: 'image/png', // Different media type
|
|
69
|
+
url: 'https://example.com',
|
|
70
|
+
supportedUrls: { 'text/plain': [/https:\/\/example\.com/] },
|
|
71
|
+
}),
|
|
72
|
+
).toBe(false);
|
|
73
|
+
});
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
describe('when the model supports URLs via wildcard media type (*)', () => {
|
|
77
|
+
it('should return true for wildcard media type and exact URL match', async () => {
|
|
78
|
+
expect(
|
|
79
|
+
isUrlSupported({
|
|
80
|
+
mediaType: 'text/plain',
|
|
81
|
+
url: 'https://example.com',
|
|
82
|
+
supportedUrls: { '*': [/https:\/\/example\.com/] },
|
|
83
|
+
}),
|
|
84
|
+
).toBe(true);
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
it('should return true for wildcard media type and regex URL match', async () => {
|
|
88
|
+
expect(
|
|
89
|
+
isUrlSupported({
|
|
90
|
+
mediaType: 'image/jpeg',
|
|
91
|
+
url: 'https://images.example.com/dog.jpg',
|
|
92
|
+
supportedUrls: { '*': [/https:\/\/images\.example\.com\/.+/] },
|
|
93
|
+
}),
|
|
94
|
+
).toBe(true);
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
it('should return false for wildcard media type but URL mismatch', async () => {
|
|
98
|
+
expect(
|
|
99
|
+
isUrlSupported({
|
|
100
|
+
mediaType: 'video/mp4',
|
|
101
|
+
url: 'https://another.com',
|
|
102
|
+
supportedUrls: { '*': [/https:\/\/example\.com/] },
|
|
103
|
+
}),
|
|
104
|
+
).toBe(false);
|
|
105
|
+
});
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
describe('when both specific and wildcard media types are defined', () => {
|
|
109
|
+
const supportedUrls = {
|
|
110
|
+
'text/plain': [/https:\/\/text\.com/],
|
|
111
|
+
'*': [/https:\/\/any\.com/],
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
it('should return true if URL matches under specific media type', async () => {
|
|
115
|
+
expect(
|
|
116
|
+
isUrlSupported({
|
|
117
|
+
mediaType: 'text/plain',
|
|
118
|
+
url: 'https://text.com',
|
|
119
|
+
supportedUrls,
|
|
120
|
+
}),
|
|
121
|
+
).toBe(true);
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
it('should return true if URL matches under wildcard media type even if specific exists', async () => {
|
|
125
|
+
// Assumes the logic checks specific first, then falls back to wildcard
|
|
126
|
+
expect(
|
|
127
|
+
isUrlSupported({
|
|
128
|
+
mediaType: 'text/plain', // Specific type exists
|
|
129
|
+
url: 'https://any.com', // Matches wildcard
|
|
130
|
+
supportedUrls,
|
|
131
|
+
}),
|
|
132
|
+
).toBe(true);
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
it('should return true if URL matches under wildcard for a non-specified media type', async () => {
|
|
136
|
+
expect(
|
|
137
|
+
isUrlSupported({
|
|
138
|
+
mediaType: 'image/png', // No specific entry for this type
|
|
139
|
+
url: 'https://any.com', // Matches wildcard
|
|
140
|
+
supportedUrls,
|
|
141
|
+
}),
|
|
142
|
+
).toBe(true);
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
it('should return false if URL matches neither specific nor wildcard', async () => {
|
|
146
|
+
expect(
|
|
147
|
+
isUrlSupported({
|
|
148
|
+
mediaType: 'text/plain',
|
|
149
|
+
url: 'https://other.com',
|
|
150
|
+
supportedUrls,
|
|
151
|
+
}),
|
|
152
|
+
).toBe(false);
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
it('should return false if URL does not match wildcard for a non-specified media type', async () => {
|
|
156
|
+
expect(
|
|
157
|
+
isUrlSupported({
|
|
158
|
+
mediaType: 'image/png',
|
|
159
|
+
url: 'https://other.com',
|
|
160
|
+
supportedUrls,
|
|
161
|
+
}),
|
|
162
|
+
).toBe(false);
|
|
163
|
+
});
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
describe('edge cases', () => {
|
|
167
|
+
it('should return true if an empty URL matches a pattern', async () => {
|
|
168
|
+
expect(
|
|
169
|
+
isUrlSupported({
|
|
170
|
+
mediaType: 'text/plain',
|
|
171
|
+
url: '',
|
|
172
|
+
supportedUrls: { 'text/plain': [/.*/] }, // Matches any string, including empty
|
|
173
|
+
}),
|
|
174
|
+
).toBe(true);
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
it('should return false if an empty URL does not match a pattern', async () => {
|
|
178
|
+
expect(
|
|
179
|
+
isUrlSupported({
|
|
180
|
+
mediaType: 'text/plain',
|
|
181
|
+
url: '',
|
|
182
|
+
supportedUrls: { 'text/plain': [/https:\/\/.+/] }, // Requires non-empty string
|
|
183
|
+
}),
|
|
184
|
+
).toBe(false);
|
|
185
|
+
});
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
describe('case sensitivity', () => {
|
|
189
|
+
it('should be case-insensitive for media types', async () => {
|
|
190
|
+
expect(
|
|
191
|
+
isUrlSupported({
|
|
192
|
+
mediaType: 'TEXT/PLAIN', // Uppercase
|
|
193
|
+
url: 'https://example.com',
|
|
194
|
+
supportedUrls: { 'text/plain': [/https:\/\/example\.com/] }, // Lowercase
|
|
195
|
+
}),
|
|
196
|
+
).toBe(true);
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
it('should handle case-insensitive regex for URLs if specified', async () => {
|
|
200
|
+
expect(
|
|
201
|
+
isUrlSupported({
|
|
202
|
+
mediaType: 'text/plain',
|
|
203
|
+
url: 'https://EXAMPLE.com/path', // Uppercase domain
|
|
204
|
+
supportedUrls: { 'text/plain': [/https:\/\/example\.com\/path/] },
|
|
205
|
+
}),
|
|
206
|
+
).toBe(true);
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
it('should be case-insensitive for URL paths by default regex', async () => {
|
|
210
|
+
expect(
|
|
211
|
+
isUrlSupported({
|
|
212
|
+
mediaType: 'text/plain',
|
|
213
|
+
url: 'https://example.com/PATH', // Uppercase path
|
|
214
|
+
supportedUrls: { 'text/plain': [/https:\/\/example\.com\/path/] }, // Lowercase path in regex
|
|
215
|
+
}),
|
|
216
|
+
).toBe(true);
|
|
217
|
+
});
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
describe('wildcard subtypes in media types', () => {
|
|
221
|
+
it('should return true for wildcard subtype match', async () => {
|
|
222
|
+
expect(
|
|
223
|
+
isUrlSupported({
|
|
224
|
+
mediaType: 'image/png',
|
|
225
|
+
url: 'https://example.com',
|
|
226
|
+
supportedUrls: { 'image/*': [/https:\/\/example\.com/] },
|
|
227
|
+
}),
|
|
228
|
+
).toBe(true);
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
it('should use full wildcard "*" if subtype wildcard is not matched or supported', async () => {
|
|
232
|
+
expect(
|
|
233
|
+
isUrlSupported({
|
|
234
|
+
mediaType: 'image/png',
|
|
235
|
+
url: 'https://any.com',
|
|
236
|
+
supportedUrls: {
|
|
237
|
+
'image/*': [/https:\/\/images\.com/], // Doesn't match URL
|
|
238
|
+
'*': [/https:\/\/any\.com/], // Matches URL
|
|
239
|
+
},
|
|
240
|
+
}),
|
|
241
|
+
).toBe(true);
|
|
242
|
+
});
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
describe('empty URL arrays for a media type', () => {
|
|
246
|
+
it('should return false if the specific media type has an empty URL array', async () => {
|
|
247
|
+
expect(
|
|
248
|
+
isUrlSupported({
|
|
249
|
+
mediaType: 'text/plain',
|
|
250
|
+
url: 'https://example.com',
|
|
251
|
+
supportedUrls: { 'text/plain': [] },
|
|
252
|
+
}),
|
|
253
|
+
).toBe(false);
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
it('should fall back to wildcard "*" if specific media type has empty array but wildcard matches', async () => {
|
|
257
|
+
expect(
|
|
258
|
+
isUrlSupported({
|
|
259
|
+
mediaType: 'text/plain',
|
|
260
|
+
url: 'https://any.com',
|
|
261
|
+
supportedUrls: {
|
|
262
|
+
'text/plain': [],
|
|
263
|
+
'*': [/https:\/\/any\.com/],
|
|
264
|
+
},
|
|
265
|
+
}),
|
|
266
|
+
).toBe(true);
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
it('should return false if specific media type has empty array and wildcard does not match', async () => {
|
|
270
|
+
expect(
|
|
271
|
+
isUrlSupported({
|
|
272
|
+
mediaType: 'text/plain',
|
|
273
|
+
url: 'https://another.com',
|
|
274
|
+
supportedUrls: {
|
|
275
|
+
'text/plain': [],
|
|
276
|
+
'*': [/https:\/\/any\.com/],
|
|
277
|
+
},
|
|
278
|
+
}),
|
|
279
|
+
).toBe(false);
|
|
280
|
+
});
|
|
281
|
+
});
|
|
282
|
+
});
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Checks if the given URL is supported natively by the model.
|
|
3
|
+
*
|
|
4
|
+
* @param mediaType - The media type of the URL. Case-sensitive.
|
|
5
|
+
* @param url - The URL to check.
|
|
6
|
+
* @param supportedUrls - A record where keys are case-sensitive media types (or '*')
|
|
7
|
+
* and values are arrays of RegExp patterns for URLs.
|
|
8
|
+
*
|
|
9
|
+
* @returns `true` if the URL matches a pattern under the specific media type
|
|
10
|
+
* or the wildcard '*', `false` otherwise.
|
|
11
|
+
*/
|
|
12
|
+
export function isUrlSupported({
|
|
13
|
+
mediaType,
|
|
14
|
+
url,
|
|
15
|
+
supportedUrls,
|
|
16
|
+
}: {
|
|
17
|
+
mediaType: string;
|
|
18
|
+
url: string;
|
|
19
|
+
supportedUrls: Record<string, RegExp[]>;
|
|
20
|
+
}): boolean {
|
|
21
|
+
// standardize media type and url to lower case
|
|
22
|
+
url = url.toLowerCase();
|
|
23
|
+
mediaType = mediaType.toLowerCase();
|
|
24
|
+
|
|
25
|
+
return (
|
|
26
|
+
Object.entries(supportedUrls)
|
|
27
|
+
// standardize supported url map into lowercase prefixes:
|
|
28
|
+
.map(([key, value]) => {
|
|
29
|
+
const mediaType = key.toLowerCase();
|
|
30
|
+
return mediaType === '*' || mediaType === '*/*'
|
|
31
|
+
? { mediaTypePrefix: '', regexes: value }
|
|
32
|
+
: { mediaTypePrefix: mediaType.replace(/\*/, ''), regexes: value };
|
|
33
|
+
})
|
|
34
|
+
// gather all regexp pattern from matched media type prefixes:
|
|
35
|
+
.filter(({ mediaTypePrefix }) => mediaType.startsWith(mediaTypePrefix))
|
|
36
|
+
.flatMap(({ regexes }) => regexes)
|
|
37
|
+
// check if any pattern matches the url:
|
|
38
|
+
.some(pattern => pattern.test(url))
|
|
39
|
+
);
|
|
40
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { LoadAPIKeyError } from '@ai-sdk/provider';
|
|
2
|
+
|
|
3
|
+
export function loadApiKey({
|
|
4
|
+
apiKey,
|
|
5
|
+
environmentVariableName,
|
|
6
|
+
apiKeyParameterName = 'apiKey',
|
|
7
|
+
description,
|
|
8
|
+
}: {
|
|
9
|
+
apiKey: string | undefined;
|
|
10
|
+
environmentVariableName: string;
|
|
11
|
+
apiKeyParameterName?: string;
|
|
12
|
+
description: string;
|
|
13
|
+
}): string {
|
|
14
|
+
if (typeof apiKey === 'string') {
|
|
15
|
+
return apiKey;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
if (apiKey != null) {
|
|
19
|
+
throw new LoadAPIKeyError({
|
|
20
|
+
message: `${description} API key must be a string.`,
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
if (typeof process === 'undefined') {
|
|
25
|
+
throw new LoadAPIKeyError({
|
|
26
|
+
message: `${description} API key is missing. Pass it using the '${apiKeyParameterName}' parameter. Environment variables is not supported in this environment.`,
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
apiKey = process.env[environmentVariableName];
|
|
31
|
+
|
|
32
|
+
if (apiKey == null) {
|
|
33
|
+
throw new LoadAPIKeyError({
|
|
34
|
+
message: `${description} API key is missing. Pass it using the '${apiKeyParameterName}' parameter or the ${environmentVariableName} environment variable.`,
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
if (typeof apiKey !== 'string') {
|
|
39
|
+
throw new LoadAPIKeyError({
|
|
40
|
+
message: `${description} API key must be a string. The value of the ${environmentVariableName} environment variable is not a string.`,
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
return apiKey;
|
|
45
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Loads an optional `string` setting from the environment or a parameter.
|
|
3
|
+
*
|
|
4
|
+
* @param settingValue - The setting value.
|
|
5
|
+
* @param environmentVariableName - The environment variable name.
|
|
6
|
+
* @returns The setting value.
|
|
7
|
+
*/
|
|
8
|
+
export function loadOptionalSetting({
|
|
9
|
+
settingValue,
|
|
10
|
+
environmentVariableName,
|
|
11
|
+
}: {
|
|
12
|
+
settingValue: string | undefined;
|
|
13
|
+
environmentVariableName: string;
|
|
14
|
+
}): string | undefined {
|
|
15
|
+
if (typeof settingValue === 'string') {
|
|
16
|
+
return settingValue;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
if (settingValue != null || typeof process === 'undefined') {
|
|
20
|
+
return undefined;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
settingValue = process.env[environmentVariableName];
|
|
24
|
+
|
|
25
|
+
if (settingValue == null || typeof settingValue !== 'string') {
|
|
26
|
+
return undefined;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
return settingValue;
|
|
30
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { LoadSettingError } from '@ai-sdk/provider';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Loads a `string` setting from the environment or a parameter.
|
|
5
|
+
*
|
|
6
|
+
* @param settingValue - The setting value.
|
|
7
|
+
* @param environmentVariableName - The environment variable name.
|
|
8
|
+
* @param settingName - The setting name.
|
|
9
|
+
* @param description - The description of the setting.
|
|
10
|
+
* @returns The setting value.
|
|
11
|
+
*/
|
|
12
|
+
export function loadSetting({
|
|
13
|
+
settingValue,
|
|
14
|
+
environmentVariableName,
|
|
15
|
+
settingName,
|
|
16
|
+
description,
|
|
17
|
+
}: {
|
|
18
|
+
settingValue: string | undefined;
|
|
19
|
+
environmentVariableName: string;
|
|
20
|
+
settingName: string;
|
|
21
|
+
description: string;
|
|
22
|
+
}): string {
|
|
23
|
+
if (typeof settingValue === 'string') {
|
|
24
|
+
return settingValue;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
if (settingValue != null) {
|
|
28
|
+
throw new LoadSettingError({
|
|
29
|
+
message: `${description} setting must be a string.`,
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
if (typeof process === 'undefined') {
|
|
34
|
+
throw new LoadSettingError({
|
|
35
|
+
message:
|
|
36
|
+
`${description} setting is missing. ` +
|
|
37
|
+
`Pass it using the '${settingName}' parameter. ` +
|
|
38
|
+
`Environment variables is not supported in this environment.`,
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
settingValue = process.env[environmentVariableName];
|
|
43
|
+
|
|
44
|
+
if (settingValue == null) {
|
|
45
|
+
throw new LoadSettingError({
|
|
46
|
+
message:
|
|
47
|
+
`${description} setting is missing. ` +
|
|
48
|
+
`Pass it using the '${settingName}' parameter ` +
|
|
49
|
+
`or the ${environmentVariableName} environment variable.`,
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
if (typeof settingValue !== 'string') {
|
|
54
|
+
throw new LoadSettingError({
|
|
55
|
+
message:
|
|
56
|
+
`${description} setting must be a string. ` +
|
|
57
|
+
`The value of the ${environmentVariableName} environment variable is not a string.`,
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return settingValue;
|
|
62
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { mediaTypeToExtension } from './media-type-to-extension';
|
|
3
|
+
|
|
4
|
+
describe('mediaTypeToExtension()', () => {
|
|
5
|
+
it.each([
|
|
6
|
+
// most common
|
|
7
|
+
['audio/mpeg', 'mp3'],
|
|
8
|
+
['audio/mp3', 'mp3'],
|
|
9
|
+
['audio/wav', 'wav'],
|
|
10
|
+
['audio/x-wav', 'wav'],
|
|
11
|
+
['audio/webm', 'webm'],
|
|
12
|
+
['audio/ogg', 'ogg'],
|
|
13
|
+
['audio/opus', 'ogg'],
|
|
14
|
+
['audio/mp4', 'm4a'],
|
|
15
|
+
['audio/x-m4a', 'm4a'],
|
|
16
|
+
['audio/flac', 'flac'],
|
|
17
|
+
['audio/aac', 'aac'],
|
|
18
|
+
// upper case
|
|
19
|
+
['AUDIO/MPEG', 'mp3'],
|
|
20
|
+
['AUDIO/MP3', 'mp3'],
|
|
21
|
+
// invalid
|
|
22
|
+
['nope', ''],
|
|
23
|
+
])('should map %s to %s', (mediaType, expectedExtension) => {
|
|
24
|
+
expect(mediaTypeToExtension(mediaType)).toBe(expectedExtension);
|
|
25
|
+
});
|
|
26
|
+
});
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Maps a media type to its corresponding file extension.
|
|
3
|
+
* It was originally introduced to set a filename for audio file uploads
|
|
4
|
+
* in https://github.com/vercel/ai/pull/8159.
|
|
5
|
+
*
|
|
6
|
+
* @param mediaType The media type to map.
|
|
7
|
+
* @returns The corresponding file extension
|
|
8
|
+
* @see https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/MIME_types/Common_types
|
|
9
|
+
*/
|
|
10
|
+
export function mediaTypeToExtension(mediaType: string) {
|
|
11
|
+
const [_type, subtype = ''] = mediaType.toLowerCase().split('/');
|
|
12
|
+
|
|
13
|
+
return (
|
|
14
|
+
{
|
|
15
|
+
mpeg: 'mp3',
|
|
16
|
+
'x-wav': 'wav',
|
|
17
|
+
opus: 'ogg',
|
|
18
|
+
mp4: 'm4a',
|
|
19
|
+
'x-m4a': 'm4a',
|
|
20
|
+
}[subtype] ?? subtype
|
|
21
|
+
);
|
|
22
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
|
|
3
|
+
import { normalizeHeaders } from './normalize-headers';
|
|
4
|
+
|
|
5
|
+
describe('normalizeHeaders', () => {
|
|
6
|
+
it('returns empty object for undefined', () => {
|
|
7
|
+
expect(normalizeHeaders(undefined)).toEqual({});
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
it('converts Headers instance to record', () => {
|
|
11
|
+
const headers = new Headers({
|
|
12
|
+
Authorization: 'Bearer token',
|
|
13
|
+
'X-Feature': 'beta',
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
expect(normalizeHeaders(headers)).toEqual({
|
|
17
|
+
authorization: 'Bearer token',
|
|
18
|
+
'x-feature': 'beta',
|
|
19
|
+
});
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
it('converts tuple array', () => {
|
|
23
|
+
const headers: HeadersInit = [
|
|
24
|
+
['Authorization', 'Bearer token'],
|
|
25
|
+
['X-Feature', 'beta'],
|
|
26
|
+
['X-Ignore', undefined as unknown as string],
|
|
27
|
+
];
|
|
28
|
+
|
|
29
|
+
expect(normalizeHeaders(headers)).toEqual({
|
|
30
|
+
authorization: 'Bearer token',
|
|
31
|
+
'x-feature': 'beta',
|
|
32
|
+
});
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it('converts plain record and filters nullish values', () => {
|
|
36
|
+
expect(
|
|
37
|
+
normalizeHeaders({
|
|
38
|
+
Authorization: 'Bearer token',
|
|
39
|
+
'X-Feature': undefined,
|
|
40
|
+
'Content-Type': 'application/json',
|
|
41
|
+
}),
|
|
42
|
+
).toEqual({
|
|
43
|
+
authorization: 'Bearer token',
|
|
44
|
+
'content-type': 'application/json',
|
|
45
|
+
});
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it('handles empty Headers instance', () => {
|
|
49
|
+
const headers = new Headers();
|
|
50
|
+
expect(normalizeHeaders(headers)).toEqual({});
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it('converts uppercase keys to lowercase', () => {
|
|
54
|
+
expect(
|
|
55
|
+
normalizeHeaders({
|
|
56
|
+
'CONTENT-TYPE': 'application/json',
|
|
57
|
+
'X-CUSTOM-HEADER': 'test-value',
|
|
58
|
+
}),
|
|
59
|
+
).toEqual({
|
|
60
|
+
'content-type': 'application/json',
|
|
61
|
+
'x-custom-header': 'test-value',
|
|
62
|
+
});
|
|
63
|
+
});
|
|
64
|
+
});
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Normalizes different header inputs into a plain record with lower-case keys.
|
|
3
|
+
* Entries with `undefined` or `null` values are removed.
|
|
4
|
+
*
|
|
5
|
+
* @param headers - Input headers (`Headers`, tuples array, plain record) to normalize.
|
|
6
|
+
* @returns A record containing the normalized header entries.
|
|
7
|
+
*/
|
|
8
|
+
export function normalizeHeaders(
|
|
9
|
+
headers:
|
|
10
|
+
| HeadersInit
|
|
11
|
+
| Record<string, string | undefined>
|
|
12
|
+
| Array<[string, string | undefined]>
|
|
13
|
+
| undefined,
|
|
14
|
+
): Record<string, string> {
|
|
15
|
+
if (headers == null) {
|
|
16
|
+
return {};
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const normalized: Record<string, string> = {};
|
|
20
|
+
|
|
21
|
+
if (headers instanceof Headers) {
|
|
22
|
+
headers.forEach((value, key) => {
|
|
23
|
+
normalized[key.toLowerCase()] = value;
|
|
24
|
+
});
|
|
25
|
+
} else {
|
|
26
|
+
if (!Array.isArray(headers)) {
|
|
27
|
+
headers = Object.entries(headers);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
for (const [key, value] of headers) {
|
|
31
|
+
if (value != null) {
|
|
32
|
+
normalized[key.toLowerCase()] = value;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
return normalized;
|
|
38
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import {
|
|
2
|
+
EventSourceMessage,
|
|
3
|
+
EventSourceParserStream,
|
|
4
|
+
} from 'eventsource-parser/stream';
|
|
5
|
+
import { ParseResult, safeParseJSON } from './parse-json';
|
|
6
|
+
import { FlexibleSchema } from './schema';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Parses a JSON event stream into a stream of parsed JSON objects.
|
|
10
|
+
*/
|
|
11
|
+
export function parseJsonEventStream<T>({
|
|
12
|
+
stream,
|
|
13
|
+
schema,
|
|
14
|
+
}: {
|
|
15
|
+
stream: ReadableStream<Uint8Array>;
|
|
16
|
+
schema: FlexibleSchema<T>;
|
|
17
|
+
}): ReadableStream<ParseResult<T>> {
|
|
18
|
+
return stream
|
|
19
|
+
.pipeThrough(new TextDecoderStream())
|
|
20
|
+
.pipeThrough(new EventSourceParserStream())
|
|
21
|
+
.pipeThrough(
|
|
22
|
+
new TransformStream<EventSourceMessage, ParseResult<T>>({
|
|
23
|
+
async transform({ data }, controller) {
|
|
24
|
+
// ignore the 'DONE' event that e.g. OpenAI sends:
|
|
25
|
+
if (data === '[DONE]') {
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
controller.enqueue(await safeParseJSON({ text: data, schema }));
|
|
30
|
+
},
|
|
31
|
+
}),
|
|
32
|
+
);
|
|
33
|
+
}
|