@ai-sdk/provider-utils 4.0.8 → 4.0.10
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 +29 -1
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +29 -1
- package/dist/index.mjs.map +1 -1
- package/package.json +6 -2
- package/src/handle-fetch-error.ts +33 -0
- package/src/__snapshots__/schema.test.ts.snap +0 -346
- package/src/add-additional-properties-to-json-schema.test.ts +0 -289
- package/src/convert-async-iterator-to-readable-stream.test.ts +0 -78
- package/src/convert-image-model-file-to-data-uri.test.ts +0 -85
- package/src/convert-to-form-data.test.ts +0 -167
- package/src/create-tool-name-mapping.test.ts +0 -163
- package/src/delay.test.ts +0 -212
- package/src/delayed-promise.test.ts +0 -132
- package/src/download-blob.test.ts +0 -145
- package/src/generate-id.test.ts +0 -31
- package/src/get-from-api.test.ts +0 -199
- package/src/get-runtime-environment-user-agent.test.ts +0 -47
- package/src/inject-json-instruction.test.ts +0 -404
- package/src/is-url-supported.test.ts +0 -282
- package/src/media-type-to-extension.test.ts +0 -26
- package/src/normalize-headers.test.ts +0 -64
- package/src/parse-json.test.ts +0 -191
- package/src/remove-undefined-entries.test.ts +0 -57
- package/src/resolve.test.ts +0 -125
- package/src/response-handler.test.ts +0 -89
- package/src/schema.test-d.ts +0 -11
- package/src/schema.test.ts +0 -502
- package/src/secure-json-parse.test.ts +0 -59
- package/src/to-json-schema/zod3-to-json-schema/parse-def.test.ts +0 -224
- package/src/to-json-schema/zod3-to-json-schema/parsers/array.test.ts +0 -98
- package/src/to-json-schema/zod3-to-json-schema/parsers/bigint.test.ts +0 -51
- package/src/to-json-schema/zod3-to-json-schema/parsers/branded.test.ts +0 -16
- package/src/to-json-schema/zod3-to-json-schema/parsers/catch.test.ts +0 -15
- package/src/to-json-schema/zod3-to-json-schema/parsers/date.test.ts +0 -97
- package/src/to-json-schema/zod3-to-json-schema/parsers/default.test.ts +0 -54
- package/src/to-json-schema/zod3-to-json-schema/parsers/effects.test.ts +0 -41
- package/src/to-json-schema/zod3-to-json-schema/parsers/intersection.test.ts +0 -92
- package/src/to-json-schema/zod3-to-json-schema/parsers/map.test.ts +0 -48
- package/src/to-json-schema/zod3-to-json-schema/parsers/native-enum.test.ts +0 -102
- package/src/to-json-schema/zod3-to-json-schema/parsers/nullable.test.ts +0 -67
- package/src/to-json-schema/zod3-to-json-schema/parsers/number.test.ts +0 -65
- package/src/to-json-schema/zod3-to-json-schema/parsers/object.test.ts +0 -149
- package/src/to-json-schema/zod3-to-json-schema/parsers/optional.test.ts +0 -147
- package/src/to-json-schema/zod3-to-json-schema/parsers/pipe.test.ts +0 -35
- package/src/to-json-schema/zod3-to-json-schema/parsers/promise.test.ts +0 -15
- package/src/to-json-schema/zod3-to-json-schema/parsers/readonly.test.ts +0 -20
- package/src/to-json-schema/zod3-to-json-schema/parsers/record.test.ts +0 -108
- package/src/to-json-schema/zod3-to-json-schema/parsers/set.test.ts +0 -20
- package/src/to-json-schema/zod3-to-json-schema/parsers/string.test.ts +0 -438
- package/src/to-json-schema/zod3-to-json-schema/parsers/tuple.test.ts +0 -33
- package/src/to-json-schema/zod3-to-json-schema/parsers/union.test.ts +0 -226
- package/src/to-json-schema/zod3-to-json-schema/refs.test.ts +0 -919
- package/src/to-json-schema/zod3-to-json-schema/zod3-to-json-schema.test.ts +0 -862
- package/src/types/tool.test-d.ts +0 -228
- package/src/validate-types.test.ts +0 -105
- package/src/with-user-agent-suffix.test.ts +0 -84
|
@@ -1,78 +0,0 @@
|
|
|
1
|
-
import { convertAsyncIteratorToReadableStream } from './convert-async-iterator-to-readable-stream';
|
|
2
|
-
import { describe, it, expect } from 'vitest';
|
|
3
|
-
|
|
4
|
-
async function* makeGenerator(onFinally: () => void) {
|
|
5
|
-
try {
|
|
6
|
-
let i = 0;
|
|
7
|
-
while (true) {
|
|
8
|
-
await new Promise(r => setTimeout(r, 0));
|
|
9
|
-
yield i++;
|
|
10
|
-
}
|
|
11
|
-
} finally {
|
|
12
|
-
onFinally();
|
|
13
|
-
}
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
describe('convertAsyncIteratorToReadableStream', () => {
|
|
17
|
-
it('calls iterator.return() on cancel and triggers finally', async () => {
|
|
18
|
-
let finallyCalled = false;
|
|
19
|
-
const it = makeGenerator(() => {
|
|
20
|
-
finallyCalled = true;
|
|
21
|
-
});
|
|
22
|
-
const stream = convertAsyncIteratorToReadableStream(it);
|
|
23
|
-
const reader = stream.getReader();
|
|
24
|
-
|
|
25
|
-
await reader.read();
|
|
26
|
-
|
|
27
|
-
await reader.cancel('stop');
|
|
28
|
-
|
|
29
|
-
// give microtasks a tick for finally to run
|
|
30
|
-
await new Promise(r => setTimeout(r, 0));
|
|
31
|
-
|
|
32
|
-
expect(finallyCalled).toBe(true);
|
|
33
|
-
});
|
|
34
|
-
|
|
35
|
-
it('does not enqueue further values after cancel', async () => {
|
|
36
|
-
const it = makeGenerator(() => {});
|
|
37
|
-
const stream = convertAsyncIteratorToReadableStream(it);
|
|
38
|
-
const reader = stream.getReader();
|
|
39
|
-
|
|
40
|
-
await reader.read();
|
|
41
|
-
await reader.cancel('stop');
|
|
42
|
-
|
|
43
|
-
const { done, value } = await reader.read();
|
|
44
|
-
expect(done).toBe(true);
|
|
45
|
-
expect(value).toBeUndefined();
|
|
46
|
-
});
|
|
47
|
-
|
|
48
|
-
it('works with iterator without return() method', async () => {
|
|
49
|
-
const it: AsyncIterator<number> = {
|
|
50
|
-
async next() {
|
|
51
|
-
return { value: 42, done: false };
|
|
52
|
-
},
|
|
53
|
-
};
|
|
54
|
-
const stream = convertAsyncIteratorToReadableStream(it);
|
|
55
|
-
const reader = stream.getReader();
|
|
56
|
-
|
|
57
|
-
const { value } = await reader.read();
|
|
58
|
-
expect(value).toBe(42);
|
|
59
|
-
|
|
60
|
-
await expect(reader.cancel()).resolves.toBeUndefined();
|
|
61
|
-
});
|
|
62
|
-
|
|
63
|
-
it('ignores errors from iterator.return()', async () => {
|
|
64
|
-
const it: AsyncIterator<number> = {
|
|
65
|
-
async next() {
|
|
66
|
-
return { value: 1, done: false };
|
|
67
|
-
},
|
|
68
|
-
async return() {
|
|
69
|
-
throw new Error('return() failed');
|
|
70
|
-
},
|
|
71
|
-
};
|
|
72
|
-
const stream = convertAsyncIteratorToReadableStream(it);
|
|
73
|
-
const reader = stream.getReader();
|
|
74
|
-
|
|
75
|
-
await reader.read();
|
|
76
|
-
await expect(reader.cancel()).resolves.toBeUndefined();
|
|
77
|
-
});
|
|
78
|
-
});
|
|
@@ -1,85 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect } from 'vitest';
|
|
2
|
-
import { convertImageModelFileToDataUri } from './convert-image-model-file-to-data-uri';
|
|
3
|
-
|
|
4
|
-
describe('convertImageModelFileToDataUri()', () => {
|
|
5
|
-
describe('URL files', () => {
|
|
6
|
-
it('should return the URL as-is for URL type files', () => {
|
|
7
|
-
const result = convertImageModelFileToDataUri({
|
|
8
|
-
type: 'url',
|
|
9
|
-
url: 'https://example.com/image.png',
|
|
10
|
-
});
|
|
11
|
-
|
|
12
|
-
expect(result).toBe('https://example.com/image.png');
|
|
13
|
-
});
|
|
14
|
-
|
|
15
|
-
it('should handle URLs with query parameters', () => {
|
|
16
|
-
const result = convertImageModelFileToDataUri({
|
|
17
|
-
type: 'url',
|
|
18
|
-
url: 'https://example.com/image.png?width=100&height=200',
|
|
19
|
-
});
|
|
20
|
-
|
|
21
|
-
expect(result).toBe('https://example.com/image.png?width=100&height=200');
|
|
22
|
-
});
|
|
23
|
-
});
|
|
24
|
-
|
|
25
|
-
describe('base64 string files', () => {
|
|
26
|
-
it('should return a data URI for base64 string data', () => {
|
|
27
|
-
const result = convertImageModelFileToDataUri({
|
|
28
|
-
type: 'file',
|
|
29
|
-
mediaType: 'image/png',
|
|
30
|
-
data: 'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==',
|
|
31
|
-
});
|
|
32
|
-
|
|
33
|
-
expect(result).toBe(
|
|
34
|
-
'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==',
|
|
35
|
-
);
|
|
36
|
-
});
|
|
37
|
-
|
|
38
|
-
it('should handle different media types', () => {
|
|
39
|
-
const result = convertImageModelFileToDataUri({
|
|
40
|
-
type: 'file',
|
|
41
|
-
mediaType: 'image/jpeg',
|
|
42
|
-
data: 'base64data',
|
|
43
|
-
});
|
|
44
|
-
|
|
45
|
-
expect(result).toBe('data:image/jpeg;base64,base64data');
|
|
46
|
-
});
|
|
47
|
-
});
|
|
48
|
-
|
|
49
|
-
describe('Uint8Array files', () => {
|
|
50
|
-
it('should convert Uint8Array to base64 and return a data URI', () => {
|
|
51
|
-
// "Hello" in bytes
|
|
52
|
-
const data = new Uint8Array([72, 101, 108, 108, 111]);
|
|
53
|
-
|
|
54
|
-
const result = convertImageModelFileToDataUri({
|
|
55
|
-
type: 'file',
|
|
56
|
-
mediaType: 'image/png',
|
|
57
|
-
data,
|
|
58
|
-
});
|
|
59
|
-
|
|
60
|
-
expect(result).toBe('data:image/png;base64,SGVsbG8=');
|
|
61
|
-
});
|
|
62
|
-
|
|
63
|
-
it('should handle empty Uint8Array', () => {
|
|
64
|
-
const result = convertImageModelFileToDataUri({
|
|
65
|
-
type: 'file',
|
|
66
|
-
mediaType: 'image/png',
|
|
67
|
-
data: new Uint8Array([]),
|
|
68
|
-
});
|
|
69
|
-
|
|
70
|
-
expect(result).toBe('data:image/png;base64,');
|
|
71
|
-
});
|
|
72
|
-
|
|
73
|
-
it('should handle different media types with Uint8Array', () => {
|
|
74
|
-
const data = new Uint8Array([72, 101, 108, 108, 111]);
|
|
75
|
-
|
|
76
|
-
const result = convertImageModelFileToDataUri({
|
|
77
|
-
type: 'file',
|
|
78
|
-
mediaType: 'image/webp',
|
|
79
|
-
data,
|
|
80
|
-
});
|
|
81
|
-
|
|
82
|
-
expect(result).toBe('data:image/webp;base64,SGVsbG8=');
|
|
83
|
-
});
|
|
84
|
-
});
|
|
85
|
-
});
|
|
@@ -1,167 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect } from 'vitest';
|
|
2
|
-
import { convertToFormData } from './convert-to-form-data';
|
|
3
|
-
|
|
4
|
-
describe('convertToFormData()', () => {
|
|
5
|
-
describe('basic values', () => {
|
|
6
|
-
it('should add string values to form data', () => {
|
|
7
|
-
const formData = convertToFormData({
|
|
8
|
-
model: 'gpt-image-1',
|
|
9
|
-
prompt: 'A cute cat',
|
|
10
|
-
});
|
|
11
|
-
|
|
12
|
-
expect(formData.get('model')).toBe('gpt-image-1');
|
|
13
|
-
expect(formData.get('prompt')).toBe('A cute cat');
|
|
14
|
-
});
|
|
15
|
-
|
|
16
|
-
it('should add number values as strings', () => {
|
|
17
|
-
const formData = convertToFormData({
|
|
18
|
-
n: 2,
|
|
19
|
-
seed: 42,
|
|
20
|
-
});
|
|
21
|
-
|
|
22
|
-
expect(formData.get('n')).toBe('2');
|
|
23
|
-
expect(formData.get('seed')).toBe('42');
|
|
24
|
-
});
|
|
25
|
-
|
|
26
|
-
it('should add Blob values to form data', () => {
|
|
27
|
-
const blob = new Blob(['test'], { type: 'image/png' });
|
|
28
|
-
const formData = convertToFormData({
|
|
29
|
-
image: blob,
|
|
30
|
-
});
|
|
31
|
-
|
|
32
|
-
expect(formData.get('image')).toBeInstanceOf(Blob);
|
|
33
|
-
});
|
|
34
|
-
});
|
|
35
|
-
|
|
36
|
-
describe('null and undefined values', () => {
|
|
37
|
-
it('should skip null values', () => {
|
|
38
|
-
const formData = convertToFormData({
|
|
39
|
-
model: 'gpt-image-1',
|
|
40
|
-
mask: null,
|
|
41
|
-
});
|
|
42
|
-
|
|
43
|
-
expect(formData.get('model')).toBe('gpt-image-1');
|
|
44
|
-
expect(formData.has('mask')).toBe(false);
|
|
45
|
-
});
|
|
46
|
-
|
|
47
|
-
it('should skip undefined values', () => {
|
|
48
|
-
const formData = convertToFormData({
|
|
49
|
-
model: 'gpt-image-1',
|
|
50
|
-
size: undefined,
|
|
51
|
-
});
|
|
52
|
-
|
|
53
|
-
expect(formData.get('model')).toBe('gpt-image-1');
|
|
54
|
-
expect(formData.has('size')).toBe(false);
|
|
55
|
-
});
|
|
56
|
-
});
|
|
57
|
-
|
|
58
|
-
describe('array values', () => {
|
|
59
|
-
it('should add single-element arrays as single value without [] suffix', () => {
|
|
60
|
-
const blob = new Blob(['test'], { type: 'image/png' });
|
|
61
|
-
const formData = convertToFormData({
|
|
62
|
-
image: [blob],
|
|
63
|
-
});
|
|
64
|
-
|
|
65
|
-
expect(formData.get('image')).toBeInstanceOf(Blob);
|
|
66
|
-
expect(formData.has('image[]')).toBe(false);
|
|
67
|
-
});
|
|
68
|
-
|
|
69
|
-
it('should add multi-element arrays with [] suffix', () => {
|
|
70
|
-
const blob1 = new Blob(['test1'], { type: 'image/png' });
|
|
71
|
-
const blob2 = new Blob(['test2'], { type: 'image/jpeg' });
|
|
72
|
-
const formData = convertToFormData({
|
|
73
|
-
image: [blob1, blob2],
|
|
74
|
-
});
|
|
75
|
-
|
|
76
|
-
expect(formData.has('image')).toBe(false);
|
|
77
|
-
const images = formData.getAll('image[]');
|
|
78
|
-
expect(images).toHaveLength(2);
|
|
79
|
-
expect(images[0]).toBeInstanceOf(Blob);
|
|
80
|
-
expect(images[1]).toBeInstanceOf(Blob);
|
|
81
|
-
});
|
|
82
|
-
|
|
83
|
-
it('should add multi-element arrays without [] suffix when useArrayBrackets is false', () => {
|
|
84
|
-
const blob1 = new Blob(['test1'], { type: 'image/png' });
|
|
85
|
-
const blob2 = new Blob(['test2'], { type: 'image/jpeg' });
|
|
86
|
-
const formData = convertToFormData(
|
|
87
|
-
{
|
|
88
|
-
image: [blob1, blob2],
|
|
89
|
-
},
|
|
90
|
-
{ useArrayBrackets: false },
|
|
91
|
-
);
|
|
92
|
-
|
|
93
|
-
expect(formData.has('image[]')).toBe(false);
|
|
94
|
-
const images = formData.getAll('image');
|
|
95
|
-
expect(images).toHaveLength(2);
|
|
96
|
-
expect(images[0]).toBeInstanceOf(Blob);
|
|
97
|
-
expect(images[1]).toBeInstanceOf(Blob);
|
|
98
|
-
});
|
|
99
|
-
|
|
100
|
-
it('should handle empty arrays by not adding any values', () => {
|
|
101
|
-
const formData = convertToFormData({
|
|
102
|
-
model: 'test',
|
|
103
|
-
images: [],
|
|
104
|
-
});
|
|
105
|
-
|
|
106
|
-
expect(formData.get('model')).toBe('test');
|
|
107
|
-
expect(formData.has('images')).toBe(false);
|
|
108
|
-
expect(formData.has('images[]')).toBe(false);
|
|
109
|
-
});
|
|
110
|
-
|
|
111
|
-
it('should add string arrays with [] suffix', () => {
|
|
112
|
-
const formData = convertToFormData({
|
|
113
|
-
tags: ['cat', 'cute', 'animal'],
|
|
114
|
-
});
|
|
115
|
-
|
|
116
|
-
const tags = formData.getAll('tags[]');
|
|
117
|
-
expect(tags).toHaveLength(3);
|
|
118
|
-
expect(tags).toEqual(['cat', 'cute', 'animal']);
|
|
119
|
-
});
|
|
120
|
-
});
|
|
121
|
-
|
|
122
|
-
describe('type validation', () => {
|
|
123
|
-
it('should accept typed input objects', () => {
|
|
124
|
-
type ImageInput = {
|
|
125
|
-
model: string;
|
|
126
|
-
prompt: string;
|
|
127
|
-
n: number;
|
|
128
|
-
size: `${number}x${number}` | undefined;
|
|
129
|
-
};
|
|
130
|
-
|
|
131
|
-
const formData = convertToFormData<ImageInput>({
|
|
132
|
-
model: 'dall-e-3',
|
|
133
|
-
prompt: 'A sunset',
|
|
134
|
-
n: 1,
|
|
135
|
-
size: '1024x1024',
|
|
136
|
-
});
|
|
137
|
-
|
|
138
|
-
expect(formData.get('model')).toBe('dall-e-3');
|
|
139
|
-
expect(formData.get('prompt')).toBe('A sunset');
|
|
140
|
-
expect(formData.get('n')).toBe('1');
|
|
141
|
-
expect(formData.get('size')).toBe('1024x1024');
|
|
142
|
-
});
|
|
143
|
-
});
|
|
144
|
-
|
|
145
|
-
describe('mixed values', () => {
|
|
146
|
-
it('should handle a complex input with various types', () => {
|
|
147
|
-
const blob = new Blob(['image data'], { type: 'image/png' });
|
|
148
|
-
const formData = convertToFormData({
|
|
149
|
-
model: 'gpt-image-1',
|
|
150
|
-
prompt: 'Edit this image',
|
|
151
|
-
image: [blob],
|
|
152
|
-
mask: null,
|
|
153
|
-
n: 1,
|
|
154
|
-
size: '1024x1024',
|
|
155
|
-
quality: 'high',
|
|
156
|
-
});
|
|
157
|
-
|
|
158
|
-
expect(formData.get('model')).toBe('gpt-image-1');
|
|
159
|
-
expect(formData.get('prompt')).toBe('Edit this image');
|
|
160
|
-
expect(formData.get('image')).toBeInstanceOf(Blob);
|
|
161
|
-
expect(formData.has('mask')).toBe(false);
|
|
162
|
-
expect(formData.get('n')).toBe('1');
|
|
163
|
-
expect(formData.get('size')).toBe('1024x1024');
|
|
164
|
-
expect(formData.get('quality')).toBe('high');
|
|
165
|
-
});
|
|
166
|
-
});
|
|
167
|
-
});
|
|
@@ -1,163 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
LanguageModelV3FunctionTool,
|
|
3
|
-
LanguageModelV3ProviderTool,
|
|
4
|
-
} from '@ai-sdk/provider';
|
|
5
|
-
import { describe, expect, it } from 'vitest';
|
|
6
|
-
import { createToolNameMapping } from './create-tool-name-mapping';
|
|
7
|
-
|
|
8
|
-
describe('createToolNameMapping', () => {
|
|
9
|
-
it('should create mappings for provider-defined tools', () => {
|
|
10
|
-
const tools: Array<
|
|
11
|
-
LanguageModelV3FunctionTool | LanguageModelV3ProviderTool
|
|
12
|
-
> = [
|
|
13
|
-
{
|
|
14
|
-
type: 'provider',
|
|
15
|
-
id: 'anthropic.computer-use',
|
|
16
|
-
name: 'custom-computer-tool',
|
|
17
|
-
args: {},
|
|
18
|
-
},
|
|
19
|
-
{
|
|
20
|
-
type: 'provider',
|
|
21
|
-
id: 'openai.code-interpreter',
|
|
22
|
-
name: 'custom-code-tool',
|
|
23
|
-
args: {},
|
|
24
|
-
},
|
|
25
|
-
];
|
|
26
|
-
|
|
27
|
-
const providerToolNames: Record<`${string}.${string}`, string> = {
|
|
28
|
-
'anthropic.computer-use': 'computer_use',
|
|
29
|
-
'openai.code-interpreter': 'code_interpreter',
|
|
30
|
-
};
|
|
31
|
-
|
|
32
|
-
const mapping = createToolNameMapping({ tools, providerToolNames });
|
|
33
|
-
|
|
34
|
-
expect(mapping.toProviderToolName('custom-computer-tool')).toBe(
|
|
35
|
-
'computer_use',
|
|
36
|
-
);
|
|
37
|
-
expect(mapping.toProviderToolName('custom-code-tool')).toBe(
|
|
38
|
-
'code_interpreter',
|
|
39
|
-
);
|
|
40
|
-
expect(mapping.toCustomToolName('computer_use')).toBe(
|
|
41
|
-
'custom-computer-tool',
|
|
42
|
-
);
|
|
43
|
-
expect(mapping.toCustomToolName('code_interpreter')).toBe(
|
|
44
|
-
'custom-code-tool',
|
|
45
|
-
);
|
|
46
|
-
});
|
|
47
|
-
|
|
48
|
-
it('should ignore function tools', () => {
|
|
49
|
-
const tools: Array<
|
|
50
|
-
LanguageModelV3FunctionTool | LanguageModelV3ProviderTool
|
|
51
|
-
> = [
|
|
52
|
-
{
|
|
53
|
-
type: 'function',
|
|
54
|
-
name: 'my-function-tool',
|
|
55
|
-
description: 'A function tool',
|
|
56
|
-
inputSchema: { type: 'object' },
|
|
57
|
-
},
|
|
58
|
-
];
|
|
59
|
-
|
|
60
|
-
const providerToolNames: Record<`${string}.${string}`, string> = {};
|
|
61
|
-
|
|
62
|
-
const mapping = createToolNameMapping({ tools, providerToolNames });
|
|
63
|
-
|
|
64
|
-
expect(mapping.toProviderToolName('my-function-tool')).toBe(
|
|
65
|
-
'my-function-tool',
|
|
66
|
-
);
|
|
67
|
-
expect(mapping.toCustomToolName('my-function-tool')).toBe(
|
|
68
|
-
'my-function-tool',
|
|
69
|
-
);
|
|
70
|
-
});
|
|
71
|
-
|
|
72
|
-
it('should return input name when tool is not in providerToolNames', () => {
|
|
73
|
-
const tools: Array<
|
|
74
|
-
LanguageModelV3FunctionTool | LanguageModelV3ProviderTool
|
|
75
|
-
> = [
|
|
76
|
-
{
|
|
77
|
-
type: 'provider',
|
|
78
|
-
id: 'unknown.tool',
|
|
79
|
-
name: 'custom-tool',
|
|
80
|
-
args: {},
|
|
81
|
-
},
|
|
82
|
-
];
|
|
83
|
-
|
|
84
|
-
const providerToolNames: Record<`${string}.${string}`, string> = {};
|
|
85
|
-
|
|
86
|
-
const mapping = createToolNameMapping({ tools, providerToolNames });
|
|
87
|
-
|
|
88
|
-
expect(mapping.toProviderToolName('custom-tool')).toBe('custom-tool');
|
|
89
|
-
expect(mapping.toCustomToolName('unknown-name')).toBe('unknown-name');
|
|
90
|
-
});
|
|
91
|
-
|
|
92
|
-
it('should return input name when mapping does not exist', () => {
|
|
93
|
-
const tools: Array<
|
|
94
|
-
LanguageModelV3FunctionTool | LanguageModelV3ProviderTool
|
|
95
|
-
> = [
|
|
96
|
-
{
|
|
97
|
-
type: 'provider',
|
|
98
|
-
id: 'anthropic.computer-use',
|
|
99
|
-
name: 'custom-computer-tool',
|
|
100
|
-
args: {},
|
|
101
|
-
},
|
|
102
|
-
];
|
|
103
|
-
|
|
104
|
-
const providerToolNames: Record<`${string}.${string}`, string> = {
|
|
105
|
-
'anthropic.computer-use': 'computer_use',
|
|
106
|
-
};
|
|
107
|
-
|
|
108
|
-
const mapping = createToolNameMapping({ tools, providerToolNames });
|
|
109
|
-
|
|
110
|
-
expect(mapping.toProviderToolName('non-existent-tool')).toBe(
|
|
111
|
-
'non-existent-tool',
|
|
112
|
-
);
|
|
113
|
-
expect(mapping.toCustomToolName('non-existent-provider-tool')).toBe(
|
|
114
|
-
'non-existent-provider-tool',
|
|
115
|
-
);
|
|
116
|
-
});
|
|
117
|
-
|
|
118
|
-
it('should handle empty tools array', () => {
|
|
119
|
-
const tools: Array<
|
|
120
|
-
LanguageModelV3FunctionTool | LanguageModelV3ProviderTool
|
|
121
|
-
> = [];
|
|
122
|
-
|
|
123
|
-
const providerToolNames: Record<`${string}.${string}`, string> = {};
|
|
124
|
-
|
|
125
|
-
const mapping = createToolNameMapping({ tools, providerToolNames });
|
|
126
|
-
|
|
127
|
-
expect(mapping.toProviderToolName('any-tool')).toBe('any-tool');
|
|
128
|
-
expect(mapping.toCustomToolName('any-tool')).toBe('any-tool');
|
|
129
|
-
});
|
|
130
|
-
|
|
131
|
-
it('should handle mixed function and provider-defined tools', () => {
|
|
132
|
-
const tools: Array<
|
|
133
|
-
LanguageModelV3FunctionTool | LanguageModelV3ProviderTool
|
|
134
|
-
> = [
|
|
135
|
-
{
|
|
136
|
-
type: 'function',
|
|
137
|
-
name: 'function-tool',
|
|
138
|
-
description: 'A function tool',
|
|
139
|
-
inputSchema: { type: 'object' },
|
|
140
|
-
},
|
|
141
|
-
{
|
|
142
|
-
type: 'provider',
|
|
143
|
-
id: 'anthropic.computer-use',
|
|
144
|
-
name: 'provider-tool',
|
|
145
|
-
args: {},
|
|
146
|
-
},
|
|
147
|
-
];
|
|
148
|
-
|
|
149
|
-
const providerToolNames: Record<`${string}.${string}`, string> = {
|
|
150
|
-
'anthropic.computer-use': 'computer_use',
|
|
151
|
-
};
|
|
152
|
-
|
|
153
|
-
const mapping = createToolNameMapping({ tools, providerToolNames });
|
|
154
|
-
|
|
155
|
-
// Function tool should not be mapped
|
|
156
|
-
expect(mapping.toProviderToolName('function-tool')).toBe('function-tool');
|
|
157
|
-
expect(mapping.toCustomToolName('function-tool')).toBe('function-tool');
|
|
158
|
-
|
|
159
|
-
// Provider-defined tool should be mapped
|
|
160
|
-
expect(mapping.toProviderToolName('provider-tool')).toBe('computer_use');
|
|
161
|
-
expect(mapping.toCustomToolName('computer_use')).toBe('provider-tool');
|
|
162
|
-
});
|
|
163
|
-
});
|
package/src/delay.test.ts
DELETED
|
@@ -1,212 +0,0 @@
|
|
|
1
|
-
import { delay } from './delay';
|
|
2
|
-
import { describe, beforeEach, afterEach, expect, it, vi } from 'vitest';
|
|
3
|
-
|
|
4
|
-
describe('delay', () => {
|
|
5
|
-
beforeEach(() => {
|
|
6
|
-
vi.useFakeTimers();
|
|
7
|
-
});
|
|
8
|
-
|
|
9
|
-
afterEach(() => {
|
|
10
|
-
vi.useRealTimers();
|
|
11
|
-
});
|
|
12
|
-
|
|
13
|
-
describe('basic delay functionality', () => {
|
|
14
|
-
it('should resolve after the specified delay', async () => {
|
|
15
|
-
const delayPromise = delay(1000);
|
|
16
|
-
|
|
17
|
-
// Promise should not be resolved immediately
|
|
18
|
-
let resolved = false;
|
|
19
|
-
delayPromise.then(() => {
|
|
20
|
-
resolved = true;
|
|
21
|
-
});
|
|
22
|
-
|
|
23
|
-
expect(resolved).toBe(false);
|
|
24
|
-
|
|
25
|
-
// Advance timers by less than the delay
|
|
26
|
-
await vi.advanceTimersByTimeAsync(500);
|
|
27
|
-
expect(resolved).toBe(false);
|
|
28
|
-
|
|
29
|
-
// Advance timers to complete the delay
|
|
30
|
-
await vi.advanceTimersByTimeAsync(500);
|
|
31
|
-
expect(resolved).toBe(true);
|
|
32
|
-
|
|
33
|
-
// Verify the promise resolves
|
|
34
|
-
await expect(delayPromise).resolves.toBeUndefined();
|
|
35
|
-
});
|
|
36
|
-
|
|
37
|
-
it('should resolve immediately when delayInMs is null', async () => {
|
|
38
|
-
const delayPromise = delay(null);
|
|
39
|
-
await expect(delayPromise).resolves.toBeUndefined();
|
|
40
|
-
});
|
|
41
|
-
|
|
42
|
-
it('should resolve immediately when delayInMs is undefined', async () => {
|
|
43
|
-
const delayPromise = delay(undefined);
|
|
44
|
-
await expect(delayPromise).resolves.toBeUndefined();
|
|
45
|
-
});
|
|
46
|
-
|
|
47
|
-
it('should resolve immediately when delayInMs is 0', async () => {
|
|
48
|
-
const delayPromise = delay(0);
|
|
49
|
-
|
|
50
|
-
// Even with 0 delay, setTimeout is used, so we need to advance timers
|
|
51
|
-
await vi.advanceTimersByTimeAsync(0);
|
|
52
|
-
await expect(delayPromise).resolves.toBeUndefined();
|
|
53
|
-
});
|
|
54
|
-
});
|
|
55
|
-
|
|
56
|
-
describe('abort signal functionality', () => {
|
|
57
|
-
it('should reject immediately if signal is already aborted', async () => {
|
|
58
|
-
const controller = new AbortController();
|
|
59
|
-
controller.abort();
|
|
60
|
-
|
|
61
|
-
const delayPromise = delay(1000, { abortSignal: controller.signal });
|
|
62
|
-
|
|
63
|
-
await expect(delayPromise).rejects.toThrow('Delay was aborted');
|
|
64
|
-
expect(vi.getTimerCount()).toBe(0); // No timer should be set
|
|
65
|
-
});
|
|
66
|
-
|
|
67
|
-
it('should reject when signal is aborted during delay', async () => {
|
|
68
|
-
const controller = new AbortController();
|
|
69
|
-
const delayPromise = delay(1000, { abortSignal: controller.signal });
|
|
70
|
-
|
|
71
|
-
// Advance time partially
|
|
72
|
-
await vi.advanceTimersByTimeAsync(500);
|
|
73
|
-
|
|
74
|
-
// Abort the signal
|
|
75
|
-
controller.abort();
|
|
76
|
-
|
|
77
|
-
await expect(delayPromise).rejects.toThrow('Delay was aborted');
|
|
78
|
-
});
|
|
79
|
-
|
|
80
|
-
it('should clean up timeout when aborted', async () => {
|
|
81
|
-
const controller = new AbortController();
|
|
82
|
-
const delayPromise = delay(1000, { abortSignal: controller.signal });
|
|
83
|
-
|
|
84
|
-
expect(vi.getTimerCount()).toBe(1);
|
|
85
|
-
|
|
86
|
-
controller.abort();
|
|
87
|
-
|
|
88
|
-
try {
|
|
89
|
-
await delayPromise;
|
|
90
|
-
} catch {
|
|
91
|
-
// Expected to throw
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
expect(vi.getTimerCount()).toBe(0);
|
|
95
|
-
});
|
|
96
|
-
|
|
97
|
-
it('should clean up event listener when delay completes normally', async () => {
|
|
98
|
-
const controller = new AbortController();
|
|
99
|
-
const addEventListenerSpy = vi.spyOn(
|
|
100
|
-
controller.signal,
|
|
101
|
-
'addEventListener',
|
|
102
|
-
);
|
|
103
|
-
const removeEventListenerSpy = vi.spyOn(
|
|
104
|
-
controller.signal,
|
|
105
|
-
'removeEventListener',
|
|
106
|
-
);
|
|
107
|
-
|
|
108
|
-
const delayPromise = delay(1000, { abortSignal: controller.signal });
|
|
109
|
-
|
|
110
|
-
expect(addEventListenerSpy).toHaveBeenCalledWith(
|
|
111
|
-
'abort',
|
|
112
|
-
expect.any(Function),
|
|
113
|
-
);
|
|
114
|
-
|
|
115
|
-
await vi.advanceTimersByTimeAsync(1000);
|
|
116
|
-
await delayPromise;
|
|
117
|
-
|
|
118
|
-
expect(removeEventListenerSpy).toHaveBeenCalledWith(
|
|
119
|
-
'abort',
|
|
120
|
-
expect.any(Function),
|
|
121
|
-
);
|
|
122
|
-
});
|
|
123
|
-
|
|
124
|
-
it('should work without signal option', async () => {
|
|
125
|
-
const delayPromise = delay(1000);
|
|
126
|
-
|
|
127
|
-
await vi.advanceTimersByTimeAsync(1000);
|
|
128
|
-
await expect(delayPromise).resolves.toBeUndefined();
|
|
129
|
-
});
|
|
130
|
-
});
|
|
131
|
-
|
|
132
|
-
describe('error handling', () => {
|
|
133
|
-
it('should create proper DOMException for abort', async () => {
|
|
134
|
-
const controller = new AbortController();
|
|
135
|
-
controller.abort();
|
|
136
|
-
|
|
137
|
-
const delayPromise = delay(1000, { abortSignal: controller.signal });
|
|
138
|
-
|
|
139
|
-
try {
|
|
140
|
-
await delayPromise;
|
|
141
|
-
expect.fail('Should have thrown');
|
|
142
|
-
} catch (error) {
|
|
143
|
-
expect(error).toBeInstanceOf(DOMException);
|
|
144
|
-
expect((error as DOMException).message).toBe('Delay was aborted');
|
|
145
|
-
expect((error as DOMException).name).toBe('AbortError');
|
|
146
|
-
}
|
|
147
|
-
});
|
|
148
|
-
});
|
|
149
|
-
|
|
150
|
-
describe('edge cases', () => {
|
|
151
|
-
it('should handle very large delays', async () => {
|
|
152
|
-
const delayPromise = delay(Number.MAX_SAFE_INTEGER);
|
|
153
|
-
|
|
154
|
-
await vi.advanceTimersByTimeAsync(1000);
|
|
155
|
-
let resolved = false;
|
|
156
|
-
delayPromise.then(() => {
|
|
157
|
-
resolved = true;
|
|
158
|
-
});
|
|
159
|
-
|
|
160
|
-
expect(resolved).toBe(false);
|
|
161
|
-
|
|
162
|
-
// Fast forward to complete
|
|
163
|
-
await vi.advanceTimersByTimeAsync(1000);
|
|
164
|
-
await expect(delayPromise).resolves.toBeUndefined();
|
|
165
|
-
});
|
|
166
|
-
|
|
167
|
-
it('should handle negative delays (treated as 0)', async () => {
|
|
168
|
-
const delayPromise = delay(-100);
|
|
169
|
-
|
|
170
|
-
vi.advanceTimersByTime(0);
|
|
171
|
-
await expect(delayPromise).resolves.toBeUndefined();
|
|
172
|
-
});
|
|
173
|
-
|
|
174
|
-
it('should handle multiple delays simultaneously', async () => {
|
|
175
|
-
const delay1 = delay(100);
|
|
176
|
-
const delay2 = delay(200);
|
|
177
|
-
const delay3 = delay(300);
|
|
178
|
-
|
|
179
|
-
let resolved1 = false;
|
|
180
|
-
let resolved2 = false;
|
|
181
|
-
let resolved3 = false;
|
|
182
|
-
|
|
183
|
-
delay1.then(() => {
|
|
184
|
-
resolved1 = true;
|
|
185
|
-
});
|
|
186
|
-
delay2.then(() => {
|
|
187
|
-
resolved2 = true;
|
|
188
|
-
});
|
|
189
|
-
delay3.then(() => {
|
|
190
|
-
resolved3 = true;
|
|
191
|
-
});
|
|
192
|
-
|
|
193
|
-
// After 100ms, only first should resolve
|
|
194
|
-
await vi.advanceTimersByTimeAsync(100);
|
|
195
|
-
expect(resolved1).toBe(true);
|
|
196
|
-
expect(resolved2).toBe(false);
|
|
197
|
-
expect(resolved3).toBe(false);
|
|
198
|
-
|
|
199
|
-
// After 200ms, first two should resolve
|
|
200
|
-
await vi.advanceTimersByTimeAsync(100);
|
|
201
|
-
expect(resolved1).toBe(true);
|
|
202
|
-
expect(resolved2).toBe(true);
|
|
203
|
-
expect(resolved3).toBe(false);
|
|
204
|
-
|
|
205
|
-
// After 300ms, all should resolve
|
|
206
|
-
await vi.advanceTimersByTimeAsync(100);
|
|
207
|
-
expect(resolved1).toBe(true);
|
|
208
|
-
expect(resolved2).toBe(true);
|
|
209
|
-
expect(resolved3).toBe(true);
|
|
210
|
-
});
|
|
211
|
-
});
|
|
212
|
-
});
|