@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,191 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { parseJSON, safeParseJSON, isParsableJson } from './parse-json';
|
|
3
|
+
import { z } from 'zod/v4';
|
|
4
|
+
import { JSONParseError, TypeValidationError } from '@ai-sdk/provider';
|
|
5
|
+
|
|
6
|
+
describe('parseJSON', () => {
|
|
7
|
+
it('should parse basic JSON without schema', async () => {
|
|
8
|
+
const result = await parseJSON({ text: '{"foo": "bar"}' });
|
|
9
|
+
expect(result).toEqual({ foo: 'bar' });
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
it('should parse JSON with schema validation', async () => {
|
|
13
|
+
const schema = z.object({ foo: z.string() });
|
|
14
|
+
const result = await parseJSON({ text: '{"foo": "bar"}', schema });
|
|
15
|
+
expect(result).toEqual({ foo: 'bar' });
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
it('should throw JSONParseError for invalid JSON', async () => {
|
|
19
|
+
await expect(() => parseJSON({ text: 'invalid json' })).rejects.toThrow(
|
|
20
|
+
JSONParseError,
|
|
21
|
+
);
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
it('should throw TypeValidationError for schema validation failures', async () => {
|
|
25
|
+
const schema = z.object({ foo: z.number() });
|
|
26
|
+
await expect(() =>
|
|
27
|
+
parseJSON({ text: '{"foo": "bar"}', schema }),
|
|
28
|
+
).rejects.toThrow(TypeValidationError);
|
|
29
|
+
});
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
describe('safeParseJSON', () => {
|
|
33
|
+
it('should safely parse basic JSON without schema and include rawValue', async () => {
|
|
34
|
+
const result = await safeParseJSON({ text: '{"foo": "bar"}' });
|
|
35
|
+
expect(result).toEqual({
|
|
36
|
+
success: true,
|
|
37
|
+
value: { foo: 'bar' },
|
|
38
|
+
rawValue: { foo: 'bar' },
|
|
39
|
+
});
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it('should preserve rawValue even after schema transformation', async () => {
|
|
43
|
+
const schema = z.object({
|
|
44
|
+
count: z.coerce.number(),
|
|
45
|
+
});
|
|
46
|
+
const result = await safeParseJSON({
|
|
47
|
+
text: '{"count": "42"}',
|
|
48
|
+
schema,
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
expect(result).toEqual({
|
|
52
|
+
success: true,
|
|
53
|
+
value: { count: 42 },
|
|
54
|
+
rawValue: { count: '42' },
|
|
55
|
+
});
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it('should handle failed parsing with error details', async () => {
|
|
59
|
+
const result = await safeParseJSON({ text: 'invalid json' });
|
|
60
|
+
expect(result).toEqual({
|
|
61
|
+
success: false,
|
|
62
|
+
error: expect.any(JSONParseError),
|
|
63
|
+
});
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
it('should handle schema validation failures', async () => {
|
|
67
|
+
const schema = z.object({ age: z.number() });
|
|
68
|
+
const result = await safeParseJSON({
|
|
69
|
+
text: '{"age": "twenty"}',
|
|
70
|
+
schema,
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
expect(result).toEqual({
|
|
74
|
+
success: false,
|
|
75
|
+
error: expect.any(TypeValidationError),
|
|
76
|
+
rawValue: { age: 'twenty' },
|
|
77
|
+
});
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
it('should handle nested objects and preserve raw values', async () => {
|
|
81
|
+
const schema = z.object({
|
|
82
|
+
user: z.object({
|
|
83
|
+
id: z.string().transform(val => parseInt(val, 10)),
|
|
84
|
+
name: z.string(),
|
|
85
|
+
}),
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
const result = await safeParseJSON({
|
|
89
|
+
text: '{"user": {"id": "123", "name": "John"}}',
|
|
90
|
+
schema: schema as any,
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
expect(result).toEqual({
|
|
94
|
+
success: true,
|
|
95
|
+
value: { user: { id: 123, name: 'John' } },
|
|
96
|
+
rawValue: { user: { id: '123', name: 'John' } },
|
|
97
|
+
});
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
it('should handle arrays and preserve raw values', async () => {
|
|
101
|
+
const schema = z.array(z.string().transform(val => val.toUpperCase()));
|
|
102
|
+
const result = await safeParseJSON({
|
|
103
|
+
text: '["hello", "world"]',
|
|
104
|
+
schema,
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
expect(result).toEqual({
|
|
108
|
+
success: true,
|
|
109
|
+
value: ['HELLO', 'WORLD'],
|
|
110
|
+
rawValue: ['hello', 'world'],
|
|
111
|
+
});
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
it('should handle discriminated unions in schema', async () => {
|
|
115
|
+
const schema = z.discriminatedUnion('type', [
|
|
116
|
+
z.object({ type: z.literal('text'), content: z.string() }),
|
|
117
|
+
z.object({ type: z.literal('number'), value: z.number() }),
|
|
118
|
+
]);
|
|
119
|
+
|
|
120
|
+
const result = await safeParseJSON({
|
|
121
|
+
text: '{"type": "text", "content": "hello"}',
|
|
122
|
+
schema,
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
expect(result).toEqual({
|
|
126
|
+
success: true,
|
|
127
|
+
value: { type: 'text', content: 'hello' },
|
|
128
|
+
rawValue: { type: 'text', content: 'hello' },
|
|
129
|
+
});
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
it('should handle nullable fields in schema', async () => {
|
|
133
|
+
const schema = z.object({
|
|
134
|
+
id: z.string().nullish(),
|
|
135
|
+
data: z.string(),
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
const result = await safeParseJSON({
|
|
139
|
+
text: '{"id": null, "data": "test"}',
|
|
140
|
+
schema,
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
expect(result).toEqual({
|
|
144
|
+
success: true,
|
|
145
|
+
value: { id: null, data: 'test' },
|
|
146
|
+
rawValue: { id: null, data: 'test' },
|
|
147
|
+
});
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
it('should handle union types in schema', async () => {
|
|
151
|
+
const schema = z.object({
|
|
152
|
+
value: z.union([z.string(), z.number()]),
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
const result1 = await safeParseJSON({
|
|
156
|
+
text: '{"value": "test"}',
|
|
157
|
+
schema,
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
const result2 = await safeParseJSON({
|
|
161
|
+
text: '{"value": 123}',
|
|
162
|
+
schema,
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
expect(result1).toEqual({
|
|
166
|
+
success: true,
|
|
167
|
+
value: { value: 'test' },
|
|
168
|
+
rawValue: { value: 'test' },
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
expect(result2).toEqual({
|
|
172
|
+
success: true,
|
|
173
|
+
value: { value: 123 },
|
|
174
|
+
rawValue: { value: 123 },
|
|
175
|
+
});
|
|
176
|
+
});
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
describe('isParsableJson', () => {
|
|
180
|
+
it('should return true for valid JSON', () => {
|
|
181
|
+
expect(isParsableJson('{"foo": "bar"}')).toBe(true);
|
|
182
|
+
expect(isParsableJson('[1, 2, 3]')).toBe(true);
|
|
183
|
+
expect(isParsableJson('"hello"')).toBe(true);
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
it('should return false for invalid JSON', () => {
|
|
187
|
+
expect(isParsableJson('invalid')).toBe(false);
|
|
188
|
+
expect(isParsableJson('{foo: "bar"}')).toBe(false);
|
|
189
|
+
expect(isParsableJson('{"foo": }')).toBe(false);
|
|
190
|
+
});
|
|
191
|
+
});
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import {
|
|
2
|
+
JSONParseError,
|
|
3
|
+
JSONValue,
|
|
4
|
+
TypeValidationError,
|
|
5
|
+
} from '@ai-sdk/provider';
|
|
6
|
+
import { secureJsonParse } from './secure-json-parse';
|
|
7
|
+
import { safeValidateTypes, validateTypes } from './validate-types';
|
|
8
|
+
import { FlexibleSchema } from './schema';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Parses a JSON string into an unknown object.
|
|
12
|
+
*
|
|
13
|
+
* @param text - The JSON string to parse.
|
|
14
|
+
* @returns {JSONValue} - The parsed JSON object.
|
|
15
|
+
*/
|
|
16
|
+
export async function parseJSON(options: {
|
|
17
|
+
text: string;
|
|
18
|
+
schema?: undefined;
|
|
19
|
+
}): Promise<JSONValue>;
|
|
20
|
+
/**
|
|
21
|
+
* Parses a JSON string into a strongly-typed object using the provided schema.
|
|
22
|
+
*
|
|
23
|
+
* @template T - The type of the object to parse the JSON into.
|
|
24
|
+
* @param {string} text - The JSON string to parse.
|
|
25
|
+
* @param {Validator<T>} schema - The schema to use for parsing the JSON.
|
|
26
|
+
* @returns {Promise<T>} - The parsed object.
|
|
27
|
+
*/
|
|
28
|
+
export async function parseJSON<T>(options: {
|
|
29
|
+
text: string;
|
|
30
|
+
schema: FlexibleSchema<T>;
|
|
31
|
+
}): Promise<T>;
|
|
32
|
+
export async function parseJSON<T>({
|
|
33
|
+
text,
|
|
34
|
+
schema,
|
|
35
|
+
}: {
|
|
36
|
+
text: string;
|
|
37
|
+
schema?: FlexibleSchema<T>;
|
|
38
|
+
}): Promise<T> {
|
|
39
|
+
try {
|
|
40
|
+
const value = secureJsonParse(text);
|
|
41
|
+
|
|
42
|
+
if (schema == null) {
|
|
43
|
+
return value;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return validateTypes<T>({ value, schema });
|
|
47
|
+
} catch (error) {
|
|
48
|
+
if (
|
|
49
|
+
JSONParseError.isInstance(error) ||
|
|
50
|
+
TypeValidationError.isInstance(error)
|
|
51
|
+
) {
|
|
52
|
+
throw error;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
throw new JSONParseError({ text, cause: error });
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export type ParseResult<T> =
|
|
60
|
+
| { success: true; value: T; rawValue: unknown }
|
|
61
|
+
| {
|
|
62
|
+
success: false;
|
|
63
|
+
error: JSONParseError | TypeValidationError;
|
|
64
|
+
rawValue: unknown;
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Safely parses a JSON string and returns the result as an object of type `unknown`.
|
|
69
|
+
*
|
|
70
|
+
* @param text - The JSON string to parse.
|
|
71
|
+
* @returns {Promise<object>} Either an object with `success: true` and the parsed data, or an object with `success: false` and the error that occurred.
|
|
72
|
+
*/
|
|
73
|
+
export async function safeParseJSON(options: {
|
|
74
|
+
text: string;
|
|
75
|
+
schema?: undefined;
|
|
76
|
+
}): Promise<ParseResult<JSONValue>>;
|
|
77
|
+
/**
|
|
78
|
+
* Safely parses a JSON string into a strongly-typed object, using a provided schema to validate the object.
|
|
79
|
+
*
|
|
80
|
+
* @template T - The type of the object to parse the JSON into.
|
|
81
|
+
* @param {string} text - The JSON string to parse.
|
|
82
|
+
* @param {Validator<T>} schema - The schema to use for parsing the JSON.
|
|
83
|
+
* @returns An object with either a `success` flag and the parsed and typed data, or a `success` flag and an error object.
|
|
84
|
+
*/
|
|
85
|
+
export async function safeParseJSON<T>(options: {
|
|
86
|
+
text: string;
|
|
87
|
+
schema: FlexibleSchema<T>;
|
|
88
|
+
}): Promise<ParseResult<T>>;
|
|
89
|
+
export async function safeParseJSON<T>({
|
|
90
|
+
text,
|
|
91
|
+
schema,
|
|
92
|
+
}: {
|
|
93
|
+
text: string;
|
|
94
|
+
schema?: FlexibleSchema<T>;
|
|
95
|
+
}): Promise<ParseResult<T>> {
|
|
96
|
+
try {
|
|
97
|
+
const value = secureJsonParse(text);
|
|
98
|
+
|
|
99
|
+
if (schema == null) {
|
|
100
|
+
return { success: true, value: value as T, rawValue: value };
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
return await safeValidateTypes<T>({ value, schema });
|
|
104
|
+
} catch (error) {
|
|
105
|
+
return {
|
|
106
|
+
success: false,
|
|
107
|
+
error: JSONParseError.isInstance(error)
|
|
108
|
+
? error
|
|
109
|
+
: new JSONParseError({ text, cause: error }),
|
|
110
|
+
rawValue: undefined,
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
export function isParsableJson(input: string): boolean {
|
|
116
|
+
try {
|
|
117
|
+
secureJsonParse(input);
|
|
118
|
+
return true;
|
|
119
|
+
} catch {
|
|
120
|
+
return false;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { InvalidArgumentError } from '@ai-sdk/provider';
|
|
2
|
+
import { safeValidateTypes } from './validate-types';
|
|
3
|
+
import { FlexibleSchema } from './schema';
|
|
4
|
+
|
|
5
|
+
export async function parseProviderOptions<OPTIONS>({
|
|
6
|
+
provider,
|
|
7
|
+
providerOptions,
|
|
8
|
+
schema,
|
|
9
|
+
}: {
|
|
10
|
+
provider: string;
|
|
11
|
+
providerOptions: Record<string, unknown> | undefined;
|
|
12
|
+
schema: FlexibleSchema<OPTIONS>;
|
|
13
|
+
}): Promise<OPTIONS | undefined> {
|
|
14
|
+
if (providerOptions?.[provider] == null) {
|
|
15
|
+
return undefined;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const parsedProviderOptions = await safeValidateTypes<OPTIONS | undefined>({
|
|
19
|
+
value: providerOptions[provider],
|
|
20
|
+
schema,
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
if (!parsedProviderOptions.success) {
|
|
24
|
+
throw new InvalidArgumentError({
|
|
25
|
+
argument: 'providerOptions',
|
|
26
|
+
message: `invalid ${provider} provider options`,
|
|
27
|
+
cause: parsedProviderOptions.error,
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
return parsedProviderOptions.value;
|
|
32
|
+
}
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
import { APICallError } from '@ai-sdk/provider';
|
|
2
|
+
import { extractResponseHeaders } from './extract-response-headers';
|
|
3
|
+
import { FetchFunction } from './fetch-function';
|
|
4
|
+
import { handleFetchError } from './handle-fetch-error';
|
|
5
|
+
import { isAbortError } from './is-abort-error';
|
|
6
|
+
import { ResponseHandler } from './response-handler';
|
|
7
|
+
import { getRuntimeEnvironmentUserAgent } from './get-runtime-environment-user-agent';
|
|
8
|
+
import { withUserAgentSuffix } from './with-user-agent-suffix';
|
|
9
|
+
import { VERSION } from './version';
|
|
10
|
+
|
|
11
|
+
// use function to allow for mocking in tests:
|
|
12
|
+
const getOriginalFetch = () => globalThis.fetch;
|
|
13
|
+
|
|
14
|
+
export const postJsonToApi = async <T>({
|
|
15
|
+
url,
|
|
16
|
+
headers,
|
|
17
|
+
body,
|
|
18
|
+
failedResponseHandler,
|
|
19
|
+
successfulResponseHandler,
|
|
20
|
+
abortSignal,
|
|
21
|
+
fetch,
|
|
22
|
+
}: {
|
|
23
|
+
url: string;
|
|
24
|
+
headers?: Record<string, string | undefined>;
|
|
25
|
+
body: unknown;
|
|
26
|
+
failedResponseHandler: ResponseHandler<APICallError>;
|
|
27
|
+
successfulResponseHandler: ResponseHandler<T>;
|
|
28
|
+
abortSignal?: AbortSignal;
|
|
29
|
+
fetch?: FetchFunction;
|
|
30
|
+
}) =>
|
|
31
|
+
postToApi({
|
|
32
|
+
url,
|
|
33
|
+
headers: {
|
|
34
|
+
'Content-Type': 'application/json',
|
|
35
|
+
...headers,
|
|
36
|
+
},
|
|
37
|
+
body: {
|
|
38
|
+
content: JSON.stringify(body),
|
|
39
|
+
values: body,
|
|
40
|
+
},
|
|
41
|
+
failedResponseHandler,
|
|
42
|
+
successfulResponseHandler,
|
|
43
|
+
abortSignal,
|
|
44
|
+
fetch,
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
export const postFormDataToApi = async <T>({
|
|
48
|
+
url,
|
|
49
|
+
headers,
|
|
50
|
+
formData,
|
|
51
|
+
failedResponseHandler,
|
|
52
|
+
successfulResponseHandler,
|
|
53
|
+
abortSignal,
|
|
54
|
+
fetch,
|
|
55
|
+
}: {
|
|
56
|
+
url: string;
|
|
57
|
+
headers?: Record<string, string | undefined>;
|
|
58
|
+
formData: FormData;
|
|
59
|
+
failedResponseHandler: ResponseHandler<APICallError>;
|
|
60
|
+
successfulResponseHandler: ResponseHandler<T>;
|
|
61
|
+
abortSignal?: AbortSignal;
|
|
62
|
+
fetch?: FetchFunction;
|
|
63
|
+
}) =>
|
|
64
|
+
postToApi({
|
|
65
|
+
url,
|
|
66
|
+
headers,
|
|
67
|
+
body: {
|
|
68
|
+
content: formData,
|
|
69
|
+
values: Object.fromEntries((formData as any).entries()),
|
|
70
|
+
},
|
|
71
|
+
failedResponseHandler,
|
|
72
|
+
successfulResponseHandler,
|
|
73
|
+
abortSignal,
|
|
74
|
+
fetch,
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
export const postToApi = async <T>({
|
|
78
|
+
url,
|
|
79
|
+
headers = {},
|
|
80
|
+
body,
|
|
81
|
+
successfulResponseHandler,
|
|
82
|
+
failedResponseHandler,
|
|
83
|
+
abortSignal,
|
|
84
|
+
fetch = getOriginalFetch(),
|
|
85
|
+
}: {
|
|
86
|
+
url: string;
|
|
87
|
+
headers?: Record<string, string | undefined>;
|
|
88
|
+
body: {
|
|
89
|
+
content: string | FormData | Uint8Array;
|
|
90
|
+
values: unknown;
|
|
91
|
+
};
|
|
92
|
+
failedResponseHandler: ResponseHandler<Error>;
|
|
93
|
+
successfulResponseHandler: ResponseHandler<T>;
|
|
94
|
+
abortSignal?: AbortSignal;
|
|
95
|
+
fetch?: FetchFunction;
|
|
96
|
+
}) => {
|
|
97
|
+
try {
|
|
98
|
+
const response = await fetch(url, {
|
|
99
|
+
method: 'POST',
|
|
100
|
+
headers: withUserAgentSuffix(
|
|
101
|
+
headers,
|
|
102
|
+
`ai-sdk/provider-utils/${VERSION}`,
|
|
103
|
+
getRuntimeEnvironmentUserAgent(),
|
|
104
|
+
),
|
|
105
|
+
body: body.content,
|
|
106
|
+
signal: abortSignal,
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
const responseHeaders = extractResponseHeaders(response);
|
|
110
|
+
|
|
111
|
+
if (!response.ok) {
|
|
112
|
+
let errorInformation: {
|
|
113
|
+
value: Error;
|
|
114
|
+
responseHeaders?: Record<string, string> | undefined;
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
try {
|
|
118
|
+
errorInformation = await failedResponseHandler({
|
|
119
|
+
response,
|
|
120
|
+
url,
|
|
121
|
+
requestBodyValues: body.values,
|
|
122
|
+
});
|
|
123
|
+
} catch (error) {
|
|
124
|
+
if (isAbortError(error) || APICallError.isInstance(error)) {
|
|
125
|
+
throw error;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
throw new APICallError({
|
|
129
|
+
message: 'Failed to process error response',
|
|
130
|
+
cause: error,
|
|
131
|
+
statusCode: response.status,
|
|
132
|
+
url,
|
|
133
|
+
responseHeaders,
|
|
134
|
+
requestBodyValues: body.values,
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
throw errorInformation.value;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
try {
|
|
142
|
+
return await successfulResponseHandler({
|
|
143
|
+
response,
|
|
144
|
+
url,
|
|
145
|
+
requestBodyValues: body.values,
|
|
146
|
+
});
|
|
147
|
+
} catch (error) {
|
|
148
|
+
if (error instanceof Error) {
|
|
149
|
+
if (isAbortError(error) || APICallError.isInstance(error)) {
|
|
150
|
+
throw error;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
throw new APICallError({
|
|
155
|
+
message: 'Failed to process successful response',
|
|
156
|
+
cause: error,
|
|
157
|
+
statusCode: response.status,
|
|
158
|
+
url,
|
|
159
|
+
responseHeaders,
|
|
160
|
+
requestBodyValues: body.values,
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
} catch (error) {
|
|
164
|
+
throw handleFetchError({ error, url, requestBodyValues: body.values });
|
|
165
|
+
}
|
|
166
|
+
};
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import { tool, Tool, ToolExecuteFunction } from './types/tool';
|
|
2
|
+
import { FlexibleSchema } from './schema';
|
|
3
|
+
|
|
4
|
+
export type ProviderToolFactory<INPUT, ARGS extends object> = <OUTPUT>(
|
|
5
|
+
options: ARGS & {
|
|
6
|
+
execute?: ToolExecuteFunction<INPUT, OUTPUT>;
|
|
7
|
+
needsApproval?: Tool<INPUT, OUTPUT>['needsApproval'];
|
|
8
|
+
toModelOutput?: Tool<INPUT, OUTPUT>['toModelOutput'];
|
|
9
|
+
onInputStart?: Tool<INPUT, OUTPUT>['onInputStart'];
|
|
10
|
+
onInputDelta?: Tool<INPUT, OUTPUT>['onInputDelta'];
|
|
11
|
+
onInputAvailable?: Tool<INPUT, OUTPUT>['onInputAvailable'];
|
|
12
|
+
},
|
|
13
|
+
) => Tool<INPUT, OUTPUT>;
|
|
14
|
+
|
|
15
|
+
export function createProviderToolFactory<INPUT, ARGS extends object>({
|
|
16
|
+
id,
|
|
17
|
+
inputSchema,
|
|
18
|
+
}: {
|
|
19
|
+
id: `${string}.${string}`;
|
|
20
|
+
inputSchema: FlexibleSchema<INPUT>;
|
|
21
|
+
}): ProviderToolFactory<INPUT, ARGS> {
|
|
22
|
+
return <OUTPUT>({
|
|
23
|
+
execute,
|
|
24
|
+
outputSchema,
|
|
25
|
+
needsApproval,
|
|
26
|
+
toModelOutput,
|
|
27
|
+
onInputStart,
|
|
28
|
+
onInputDelta,
|
|
29
|
+
onInputAvailable,
|
|
30
|
+
...args
|
|
31
|
+
}: ARGS & {
|
|
32
|
+
execute?: ToolExecuteFunction<INPUT, OUTPUT>;
|
|
33
|
+
outputSchema?: FlexibleSchema<OUTPUT>;
|
|
34
|
+
needsApproval?: Tool<INPUT, OUTPUT>['needsApproval'];
|
|
35
|
+
toModelOutput?: Tool<INPUT, OUTPUT>['toModelOutput'];
|
|
36
|
+
onInputStart?: Tool<INPUT, OUTPUT>['onInputStart'];
|
|
37
|
+
onInputDelta?: Tool<INPUT, OUTPUT>['onInputDelta'];
|
|
38
|
+
onInputAvailable?: Tool<INPUT, OUTPUT>['onInputAvailable'];
|
|
39
|
+
}): Tool<INPUT, OUTPUT> =>
|
|
40
|
+
tool({
|
|
41
|
+
type: 'provider',
|
|
42
|
+
id,
|
|
43
|
+
args,
|
|
44
|
+
inputSchema,
|
|
45
|
+
outputSchema,
|
|
46
|
+
execute,
|
|
47
|
+
needsApproval,
|
|
48
|
+
toModelOutput,
|
|
49
|
+
onInputStart,
|
|
50
|
+
onInputDelta,
|
|
51
|
+
onInputAvailable,
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export type ProviderToolFactoryWithOutputSchema<
|
|
56
|
+
INPUT,
|
|
57
|
+
OUTPUT,
|
|
58
|
+
ARGS extends object,
|
|
59
|
+
> = (
|
|
60
|
+
options: ARGS & {
|
|
61
|
+
execute?: ToolExecuteFunction<INPUT, OUTPUT>;
|
|
62
|
+
needsApproval?: Tool<INPUT, OUTPUT>['needsApproval'];
|
|
63
|
+
toModelOutput?: Tool<INPUT, OUTPUT>['toModelOutput'];
|
|
64
|
+
onInputStart?: Tool<INPUT, OUTPUT>['onInputStart'];
|
|
65
|
+
onInputDelta?: Tool<INPUT, OUTPUT>['onInputDelta'];
|
|
66
|
+
onInputAvailable?: Tool<INPUT, OUTPUT>['onInputAvailable'];
|
|
67
|
+
},
|
|
68
|
+
) => Tool<INPUT, OUTPUT>;
|
|
69
|
+
|
|
70
|
+
export function createProviderToolFactoryWithOutputSchema<
|
|
71
|
+
INPUT,
|
|
72
|
+
OUTPUT,
|
|
73
|
+
ARGS extends object,
|
|
74
|
+
>({
|
|
75
|
+
id,
|
|
76
|
+
inputSchema,
|
|
77
|
+
outputSchema,
|
|
78
|
+
supportsDeferredResults,
|
|
79
|
+
}: {
|
|
80
|
+
id: `${string}.${string}`;
|
|
81
|
+
inputSchema: FlexibleSchema<INPUT>;
|
|
82
|
+
outputSchema: FlexibleSchema<OUTPUT>;
|
|
83
|
+
/**
|
|
84
|
+
* Whether this provider-executed tool supports deferred results.
|
|
85
|
+
*
|
|
86
|
+
* When true, the tool result may not be returned in the same turn as the
|
|
87
|
+
* tool call (e.g., when using programmatic tool calling where a server tool
|
|
88
|
+
* triggers a client-executed tool, and the server tool's result is deferred
|
|
89
|
+
* until the client tool is resolved).
|
|
90
|
+
*
|
|
91
|
+
* @default false
|
|
92
|
+
*/
|
|
93
|
+
supportsDeferredResults?: boolean;
|
|
94
|
+
}): ProviderToolFactoryWithOutputSchema<INPUT, OUTPUT, ARGS> {
|
|
95
|
+
return ({
|
|
96
|
+
execute,
|
|
97
|
+
needsApproval,
|
|
98
|
+
toModelOutput,
|
|
99
|
+
onInputStart,
|
|
100
|
+
onInputDelta,
|
|
101
|
+
onInputAvailable,
|
|
102
|
+
...args
|
|
103
|
+
}: ARGS & {
|
|
104
|
+
execute?: ToolExecuteFunction<INPUT, OUTPUT>;
|
|
105
|
+
needsApproval?: Tool<INPUT, OUTPUT>['needsApproval'];
|
|
106
|
+
toModelOutput?: Tool<INPUT, OUTPUT>['toModelOutput'];
|
|
107
|
+
onInputStart?: Tool<INPUT, OUTPUT>['onInputStart'];
|
|
108
|
+
onInputDelta?: Tool<INPUT, OUTPUT>['onInputDelta'];
|
|
109
|
+
onInputAvailable?: Tool<INPUT, OUTPUT>['onInputAvailable'];
|
|
110
|
+
}): Tool<INPUT, OUTPUT> =>
|
|
111
|
+
tool({
|
|
112
|
+
type: 'provider',
|
|
113
|
+
id,
|
|
114
|
+
args,
|
|
115
|
+
inputSchema,
|
|
116
|
+
outputSchema,
|
|
117
|
+
execute,
|
|
118
|
+
needsApproval,
|
|
119
|
+
toModelOutput,
|
|
120
|
+
onInputStart,
|
|
121
|
+
onInputDelta,
|
|
122
|
+
onInputAvailable,
|
|
123
|
+
supportsDeferredResults,
|
|
124
|
+
});
|
|
125
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { expect, it } from 'vitest';
|
|
2
|
+
import { removeUndefinedEntries } from './remove-undefined-entries';
|
|
3
|
+
|
|
4
|
+
it('should remove undefined entries from record', () => {
|
|
5
|
+
const input = {
|
|
6
|
+
a: 1,
|
|
7
|
+
b: undefined,
|
|
8
|
+
c: 'test',
|
|
9
|
+
d: undefined,
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
expect(removeUndefinedEntries(input)).toEqual({
|
|
13
|
+
a: 1,
|
|
14
|
+
c: 'test',
|
|
15
|
+
});
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
it('should handle empty object', () => {
|
|
19
|
+
const input = {};
|
|
20
|
+
expect(removeUndefinedEntries(input)).toEqual({});
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it('should handle object with all undefined values', () => {
|
|
24
|
+
const input = {
|
|
25
|
+
a: undefined,
|
|
26
|
+
b: undefined,
|
|
27
|
+
};
|
|
28
|
+
expect(removeUndefinedEntries(input)).toEqual({});
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it('should remove null values', () => {
|
|
32
|
+
// Both null and undefined will be removed.
|
|
33
|
+
const input = {
|
|
34
|
+
a: null,
|
|
35
|
+
b: undefined,
|
|
36
|
+
c: 'test',
|
|
37
|
+
};
|
|
38
|
+
expect(removeUndefinedEntries(input)).toEqual({
|
|
39
|
+
c: 'test',
|
|
40
|
+
});
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it('should preserve falsy values except null and undefined', () => {
|
|
44
|
+
// Only false, 0, and '' are preserved.
|
|
45
|
+
const input = {
|
|
46
|
+
a: false,
|
|
47
|
+
b: 0,
|
|
48
|
+
c: '',
|
|
49
|
+
d: undefined,
|
|
50
|
+
e: null,
|
|
51
|
+
};
|
|
52
|
+
expect(removeUndefinedEntries(input)).toEqual({
|
|
53
|
+
a: false,
|
|
54
|
+
b: 0,
|
|
55
|
+
c: '',
|
|
56
|
+
});
|
|
57
|
+
});
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Removes entries from a record where the value is null or undefined.
|
|
3
|
+
* @param record - The input object whose entries may be null or undefined.
|
|
4
|
+
* @returns A new object containing only entries with non-null and non-undefined values.
|
|
5
|
+
*/
|
|
6
|
+
export function removeUndefinedEntries<T>(
|
|
7
|
+
record: Record<string, T | undefined>,
|
|
8
|
+
): Record<string, T> {
|
|
9
|
+
return Object.fromEntries(
|
|
10
|
+
Object.entries(record).filter(([_key, value]) => value != null),
|
|
11
|
+
) as Record<string, T>;
|
|
12
|
+
}
|