@outputai/llm 0.6.1-next.fc6a93e.0 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +20 -12
- package/src/ai_model.js +25 -146
- package/src/ai_model.spec.js +185 -762
- package/src/ai_provider.js +86 -0
- package/src/ai_provider.spec.js +147 -0
- package/src/index.d.ts +51 -16
- package/src/index.js +1 -4
- package/src/prompt/validations.js +4 -1
- package/src/prompt/validations.spec.js +65 -0
- package/src/utils/error_handler.js +25 -8
- package/src/utils/error_handler.spec.js +17 -2
package/src/ai_model.spec.js
CHANGED
|
@@ -1,836 +1,259 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
const providerFactoryOptions = vi.hoisted( () => ( {} ) );
|
|
4
|
-
const openaiImpl = vi.hoisted( () => vi.fn( model => `openai:${model}` ) );
|
|
5
|
-
const openaiImageImpl = vi.hoisted( () => vi.fn( model => `openai-image:${model}` ) );
|
|
6
|
-
const azureImpl = vi.hoisted( () => vi.fn( model => `azure:${model}` ) );
|
|
7
|
-
const anthropicImpl = vi.hoisted( () => vi.fn( model => `anthropic:${model}` ) );
|
|
8
|
-
const bedrockImpl = vi.hoisted( () => vi.fn( model => `bedrock:${model}` ) );
|
|
9
|
-
const perplexityImpl = vi.hoisted( () => vi.fn( model => `perplexity:${model}` ) );
|
|
10
|
-
const vertexImpl = vi.hoisted( () => vi.fn( model => `vertex:${model}` ) );
|
|
11
|
-
|
|
12
|
-
// OpenAI mock with tools support
|
|
13
|
-
vi.mock( '@ai-sdk/openai', () => {
|
|
14
|
-
const openaiMock = ( ...values ) => openaiImpl( ...values );
|
|
15
|
-
openaiMock.image = ( ...values ) => openaiImageImpl( ...values );
|
|
16
|
-
openaiMock.tools = {
|
|
17
|
-
webSearch: ( config = {} ) => ( { type: 'webSearch', config } )
|
|
18
|
-
};
|
|
19
|
-
return {
|
|
20
|
-
createOpenAI: options => {
|
|
21
|
-
providerFactoryOptions.openai = options;
|
|
22
|
-
return openaiMock;
|
|
23
|
-
}
|
|
24
|
-
};
|
|
25
|
-
} );
|
|
26
|
-
|
|
27
|
-
// Azure mock without tools support
|
|
28
|
-
vi.mock( '@ai-sdk/azure', () => ( {
|
|
29
|
-
createAzure: options => {
|
|
30
|
-
providerFactoryOptions.azure = options;
|
|
31
|
-
return ( ...values ) => azureImpl( ...values );
|
|
32
|
-
}
|
|
33
|
-
} ) );
|
|
34
|
-
|
|
35
|
-
// Anthropic mock with tools support
|
|
36
|
-
vi.mock( '@ai-sdk/anthropic', () => {
|
|
37
|
-
const anthropicMock = ( ...values ) => anthropicImpl( ...values );
|
|
38
|
-
anthropicMock.tools = {
|
|
39
|
-
webSearch_20250305: ( config = {} ) => ( { type: 'webSearch_20250305', config } ),
|
|
40
|
-
bash_20241022: ( config = {} ) => ( { type: 'bash_20241022', config } ),
|
|
41
|
-
bash_20250124: ( config = {} ) => ( { type: 'bash_20250124', config } ),
|
|
42
|
-
codeExecution_20250522: ( config = {} ) => ( { type: 'codeExecution_20250522', config } ),
|
|
43
|
-
codeExecution_20250825: ( config = {} ) => ( { type: 'codeExecution_20250825', config } )
|
|
44
|
-
};
|
|
45
|
-
return {
|
|
46
|
-
createAnthropic: options => {
|
|
47
|
-
providerFactoryOptions.anthropic = options;
|
|
48
|
-
return anthropicMock;
|
|
49
|
-
}
|
|
50
|
-
};
|
|
51
|
-
} );
|
|
1
|
+
import { afterEach, describe, expect, it, vi } from 'vitest';
|
|
52
2
|
|
|
53
|
-
|
|
54
|
-
vi.mock( '@ai-sdk/amazon-bedrock', () => {
|
|
55
|
-
const bedrockMock = ( ...values ) => bedrockImpl( ...values );
|
|
56
|
-
bedrockMock.tools = {
|
|
57
|
-
bash_20241022: ( config = {} ) => ( { type: 'bash_20241022', config } ),
|
|
58
|
-
textEditor_20241022: ( config = {} ) => ( { type: 'textEditor_20241022', config } ),
|
|
59
|
-
textEditor_20250429: ( config = {} ) => ( { type: 'textEditor_20250429', config } ),
|
|
60
|
-
computer_20241022: ( config = {} ) => ( { type: 'computer_20241022', config } )
|
|
61
|
-
};
|
|
62
|
-
return {
|
|
63
|
-
createAmazonBedrock: options => {
|
|
64
|
-
providerFactoryOptions.bedrock = options;
|
|
65
|
-
return bedrockMock;
|
|
66
|
-
}
|
|
67
|
-
};
|
|
68
|
-
} );
|
|
3
|
+
const getProvider = vi.hoisted( () => vi.fn() );
|
|
69
4
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
createPerplexity: options => {
|
|
73
|
-
providerFactoryOptions.perplexity = options;
|
|
74
|
-
return ( ...values ) => perplexityImpl( ...values );
|
|
75
|
-
}
|
|
5
|
+
vi.mock( './ai_provider.js', () => ( {
|
|
6
|
+
getProvider
|
|
76
7
|
} ) );
|
|
77
8
|
|
|
78
|
-
|
|
79
|
-
vi.mock( '@ai-sdk/google-vertex', () => {
|
|
80
|
-
const vertexFn = ( ...values ) => vertexImpl( ...values );
|
|
81
|
-
vertexFn.tools = {
|
|
82
|
-
googleSearch: ( config = {} ) => ( { type: 'googleSearch', config } ),
|
|
83
|
-
fileSearch: ( config = {} ) => ( { type: 'fileSearch', config } ),
|
|
84
|
-
urlContext: ( config = {} ) => ( { type: 'urlContext', config } ),
|
|
85
|
-
enterpriseWebSearch: ( config = {} ) => ( { type: 'enterpriseWebSearch', config } ),
|
|
86
|
-
googleMaps: ( config = {} ) => ( { type: 'googleMaps', config } ),
|
|
87
|
-
codeExecution: ( config = {} ) => ( { type: 'codeExecution', config } ),
|
|
88
|
-
vertexRagStore: ( config = {} ) => ( { type: 'vertexRagStore', config } )
|
|
89
|
-
};
|
|
90
|
-
return {
|
|
91
|
-
createVertex: options => {
|
|
92
|
-
providerFactoryOptions.vertex = options;
|
|
93
|
-
return vertexFn;
|
|
94
|
-
}
|
|
95
|
-
};
|
|
96
|
-
} );
|
|
97
|
-
|
|
98
|
-
import { loadImageModel, loadTextModel, loadTools, registerProvider, getRegisteredProviders, providers, builtInProviders } from './ai_model.js';
|
|
9
|
+
import { loadImageModel, loadTextModel, loadTools } from './ai_model.js';
|
|
99
10
|
|
|
100
|
-
afterEach(
|
|
101
|
-
await vi.resetModules();
|
|
11
|
+
afterEach( () => {
|
|
102
12
|
vi.clearAllMocks();
|
|
103
13
|
} );
|
|
104
14
|
|
|
105
15
|
describe( 'loadTextModel', () => {
|
|
106
|
-
it( '
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
anthropic: { fetch: expect.any( Function ) },
|
|
110
|
-
openai: { fetch: expect.any( Function ) },
|
|
111
|
-
vertex: { fetch: expect.any( Function ) },
|
|
112
|
-
bedrock: { fetch: expect.any( Function ) },
|
|
113
|
-
perplexity: { fetch: expect.any( Function ) }
|
|
114
|
-
} );
|
|
115
|
-
} );
|
|
116
|
-
|
|
117
|
-
it( 'loads model using selected provider', () => {
|
|
118
|
-
const result = loadTextModel( { config: { provider: 'openai', model: 'gpt-4o-mini' } } );
|
|
119
|
-
|
|
120
|
-
expect( result ).toBe( 'openai:gpt-4o-mini' );
|
|
121
|
-
expect( openaiImpl ).toHaveBeenCalledWith( 'gpt-4o-mini' );
|
|
122
|
-
expect( azureImpl ).not.toHaveBeenCalled();
|
|
123
|
-
expect( anthropicImpl ).not.toHaveBeenCalled();
|
|
124
|
-
} );
|
|
16
|
+
it( 'loads a text model using the prompt provider and model', () => {
|
|
17
|
+
const provider = vi.fn( model => ( { type: 'text-model', model } ) );
|
|
18
|
+
getProvider.mockReturnValue( provider );
|
|
125
19
|
|
|
126
|
-
|
|
127
|
-
|
|
20
|
+
const result = loadTextModel( {
|
|
21
|
+
config: {
|
|
22
|
+
provider: 'openai',
|
|
23
|
+
model: 'gpt-4o-mini'
|
|
24
|
+
}
|
|
25
|
+
} );
|
|
128
26
|
|
|
129
|
-
expect(
|
|
130
|
-
expect(
|
|
27
|
+
expect( getProvider ).toHaveBeenCalledWith( 'openai' );
|
|
28
|
+
expect( provider ).toHaveBeenCalledWith( 'gpt-4o-mini' );
|
|
29
|
+
expect( result ).toEqual( {
|
|
30
|
+
type: 'text-model',
|
|
31
|
+
model: 'gpt-4o-mini'
|
|
32
|
+
} );
|
|
131
33
|
} );
|
|
132
34
|
|
|
133
|
-
it( '
|
|
134
|
-
|
|
35
|
+
it( 'propagates provider lookup errors', () => {
|
|
36
|
+
getProvider.mockImplementation( () => {
|
|
37
|
+
throw new Error( 'Unsupported provider "missing"' );
|
|
38
|
+
} );
|
|
135
39
|
|
|
136
|
-
expect(
|
|
137
|
-
|
|
40
|
+
expect( () => loadTextModel( {
|
|
41
|
+
config: {
|
|
42
|
+
provider: 'missing',
|
|
43
|
+
model: 'model'
|
|
44
|
+
}
|
|
45
|
+
} ) ).toThrow( 'Unsupported provider "missing"' );
|
|
138
46
|
} );
|
|
139
47
|
} );
|
|
140
48
|
|
|
141
49
|
describe( 'loadImageModel', () => {
|
|
142
|
-
it( 'loads image model using provider
|
|
143
|
-
const
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
expect( openaiImageImpl ).toHaveBeenCalledWith( 'gpt-image-1' );
|
|
147
|
-
expect( openaiImpl ).not.toHaveBeenCalled();
|
|
148
|
-
} );
|
|
149
|
-
|
|
150
|
-
it( 'throws a clear error when provider does not support image models', () => {
|
|
151
|
-
expect( () => loadImageModel( {
|
|
152
|
-
config: { provider: 'azure', model: 'gpt-image-1' }
|
|
153
|
-
} ) ).toThrow( 'Provider "azure" does not support image models.' );
|
|
154
|
-
} );
|
|
155
|
-
} );
|
|
156
|
-
|
|
157
|
-
describe( 'loadTools', () => {
|
|
158
|
-
// Category 1: Basic Functionality (5 tests)
|
|
159
|
-
describe( 'Basic Functionality', () => {
|
|
160
|
-
it( 'returns null when no tools configured', () => {
|
|
161
|
-
const result = loadTools( { config: { provider: 'vertex', model: 'gemini-2.0-flash' } } );
|
|
162
|
-
expect( result ).toBeNull();
|
|
163
|
-
} );
|
|
164
|
-
|
|
165
|
-
it( 'returns null when tools is empty object', () => {
|
|
166
|
-
const result = loadTools( { config: { provider: 'vertex', model: 'gemini-2.0-flash', tools: {} } } );
|
|
167
|
-
expect( result ).toBeNull();
|
|
168
|
-
} );
|
|
169
|
-
|
|
170
|
-
it( 'loads single tool with empty config', () => {
|
|
171
|
-
const result = loadTools( {
|
|
172
|
-
config: { provider: 'vertex', tools: { googleSearch: {} } }
|
|
173
|
-
} );
|
|
174
|
-
|
|
175
|
-
expect( result ).toEqual( {
|
|
176
|
-
googleSearch: { type: 'googleSearch', config: {} }
|
|
177
|
-
} );
|
|
178
|
-
} );
|
|
50
|
+
it( 'loads an image model using provider.image', () => {
|
|
51
|
+
const textProvider = vi.fn();
|
|
52
|
+
textProvider.image = vi.fn( model => ( { type: 'image-model', model } ) );
|
|
53
|
+
getProvider.mockReturnValue( textProvider );
|
|
179
54
|
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
}
|
|
186
|
-
} );
|
|
187
|
-
|
|
188
|
-
expect( result ).toEqual( {
|
|
189
|
-
googleSearch: { type: 'googleSearch', config: { mode: 'MODE_DYNAMIC' } }
|
|
190
|
-
} );
|
|
55
|
+
const result = loadImageModel( {
|
|
56
|
+
config: {
|
|
57
|
+
provider: 'openai',
|
|
58
|
+
model: 'gpt-image-1'
|
|
59
|
+
}
|
|
191
60
|
} );
|
|
192
61
|
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
urlContext: {},
|
|
200
|
-
fileSearch: { topK: 5 }
|
|
201
|
-
}
|
|
202
|
-
}
|
|
203
|
-
} );
|
|
204
|
-
|
|
205
|
-
expect( Object.keys( result ) ).toEqual( [ 'googleSearch', 'urlContext', 'fileSearch' ] );
|
|
206
|
-
expect( result.googleSearch.config ).toEqual( { mode: 'MODE_DYNAMIC' } );
|
|
207
|
-
expect( result.urlContext.config ).toEqual( {} );
|
|
208
|
-
expect( result.fileSearch.config ).toEqual( { topK: 5 } );
|
|
62
|
+
expect( getProvider ).toHaveBeenCalledWith( 'openai' );
|
|
63
|
+
expect( textProvider.image ).toHaveBeenCalledWith( 'gpt-image-1' );
|
|
64
|
+
expect( textProvider ).not.toHaveBeenCalled();
|
|
65
|
+
expect( result ).toEqual( {
|
|
66
|
+
type: 'image-model',
|
|
67
|
+
model: 'gpt-image-1'
|
|
209
68
|
} );
|
|
210
69
|
} );
|
|
211
70
|
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
config: {
|
|
217
|
-
provider: 'vertex',
|
|
218
|
-
tools: {
|
|
219
|
-
googleSearch: {
|
|
220
|
-
mode: 'MODE_DYNAMIC',
|
|
221
|
-
dynamicThreshold: 0.8
|
|
222
|
-
}
|
|
223
|
-
}
|
|
224
|
-
}
|
|
225
|
-
} );
|
|
71
|
+
it( 'falls back to provider.imageModel', () => {
|
|
72
|
+
const provider = vi.fn();
|
|
73
|
+
provider.imageModel = vi.fn( model => ( { type: 'legacy-image-model', model } ) );
|
|
74
|
+
getProvider.mockReturnValue( provider );
|
|
226
75
|
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
}
|
|
233
|
-
} );
|
|
76
|
+
const result = loadImageModel( {
|
|
77
|
+
config: {
|
|
78
|
+
provider: 'custom',
|
|
79
|
+
model: 'image-v1'
|
|
80
|
+
}
|
|
234
81
|
} );
|
|
235
82
|
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
tools: {
|
|
241
|
-
fileSearch: {
|
|
242
|
-
fileSearchStoreNames: [ 'store-1', 'store-2' ],
|
|
243
|
-
topK: 5,
|
|
244
|
-
metadataFilter: 'category = "docs"'
|
|
245
|
-
}
|
|
246
|
-
}
|
|
247
|
-
}
|
|
248
|
-
} );
|
|
249
|
-
|
|
250
|
-
expect( result.fileSearch.config ).toEqual( {
|
|
251
|
-
fileSearchStoreNames: [ 'store-1', 'store-2' ],
|
|
252
|
-
topK: 5,
|
|
253
|
-
metadataFilter: 'category = "docs"'
|
|
254
|
-
} );
|
|
255
|
-
} );
|
|
256
|
-
|
|
257
|
-
it( 'loads urlContext with empty config', () => {
|
|
258
|
-
const result = loadTools( {
|
|
259
|
-
config: {
|
|
260
|
-
provider: 'vertex',
|
|
261
|
-
tools: { urlContext: {} }
|
|
262
|
-
}
|
|
263
|
-
} );
|
|
264
|
-
|
|
265
|
-
expect( result.urlContext ).toEqual( {
|
|
266
|
-
type: 'urlContext',
|
|
267
|
-
config: {}
|
|
268
|
-
} );
|
|
269
|
-
} );
|
|
270
|
-
|
|
271
|
-
it( 'loads enterpriseWebSearch with config', () => {
|
|
272
|
-
const result = loadTools( {
|
|
273
|
-
config: {
|
|
274
|
-
provider: 'vertex',
|
|
275
|
-
tools: { enterpriseWebSearch: { threshold: 0.5 } }
|
|
276
|
-
}
|
|
277
|
-
} );
|
|
278
|
-
|
|
279
|
-
expect( result.enterpriseWebSearch.config ).toEqual( { threshold: 0.5 } );
|
|
280
|
-
} );
|
|
281
|
-
|
|
282
|
-
it( 'loads googleMaps with config', () => {
|
|
283
|
-
const result = loadTools( {
|
|
284
|
-
config: {
|
|
285
|
-
provider: 'vertex',
|
|
286
|
-
tools: { googleMaps: { region: 'US' } }
|
|
287
|
-
}
|
|
288
|
-
} );
|
|
289
|
-
|
|
290
|
-
expect( result.googleMaps.config ).toEqual( { region: 'US' } );
|
|
291
|
-
} );
|
|
292
|
-
|
|
293
|
-
it( 'loads codeExecution with config', () => {
|
|
294
|
-
const result = loadTools( {
|
|
295
|
-
config: {
|
|
296
|
-
provider: 'vertex',
|
|
297
|
-
tools: { codeExecution: { timeout: 30 } }
|
|
298
|
-
}
|
|
299
|
-
} );
|
|
300
|
-
|
|
301
|
-
expect( result.codeExecution.config ).toEqual( { timeout: 30 } );
|
|
302
|
-
} );
|
|
303
|
-
|
|
304
|
-
it( 'loads vertexRagStore with config', () => {
|
|
305
|
-
const result = loadTools( {
|
|
306
|
-
config: {
|
|
307
|
-
provider: 'vertex',
|
|
308
|
-
tools: {
|
|
309
|
-
vertexRagStore: {
|
|
310
|
-
ragCorpus: 'my-corpus-id',
|
|
311
|
-
topK: 3
|
|
312
|
-
}
|
|
313
|
-
}
|
|
314
|
-
}
|
|
315
|
-
} );
|
|
316
|
-
|
|
317
|
-
expect( result.vertexRagStore.config ).toEqual( {
|
|
318
|
-
ragCorpus: 'my-corpus-id',
|
|
319
|
-
topK: 3
|
|
320
|
-
} );
|
|
321
|
-
} );
|
|
322
|
-
|
|
323
|
-
it( 'loads multiple Vertex tools simultaneously', () => {
|
|
324
|
-
const result = loadTools( {
|
|
325
|
-
config: {
|
|
326
|
-
provider: 'vertex',
|
|
327
|
-
tools: {
|
|
328
|
-
googleSearch: { mode: 'MODE_DYNAMIC' },
|
|
329
|
-
fileSearch: { topK: 5 },
|
|
330
|
-
urlContext: {}
|
|
331
|
-
}
|
|
332
|
-
}
|
|
333
|
-
} );
|
|
334
|
-
|
|
335
|
-
expect( Object.keys( result ) ).toEqual( [ 'googleSearch', 'fileSearch', 'urlContext' ] );
|
|
336
|
-
expect( result.googleSearch.type ).toBe( 'googleSearch' );
|
|
337
|
-
expect( result.fileSearch.type ).toBe( 'fileSearch' );
|
|
338
|
-
expect( result.urlContext.type ).toBe( 'urlContext' );
|
|
83
|
+
expect( provider.imageModel ).toHaveBeenCalledWith( 'image-v1' );
|
|
84
|
+
expect( result ).toEqual( {
|
|
85
|
+
type: 'legacy-image-model',
|
|
86
|
+
model: 'image-v1'
|
|
339
87
|
} );
|
|
340
88
|
} );
|
|
341
89
|
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
provider: 'openai',
|
|
348
|
-
tools: { webSearch: {} }
|
|
349
|
-
}
|
|
350
|
-
} );
|
|
90
|
+
it( 'prefers provider.image when both image factories exist', () => {
|
|
91
|
+
const provider = vi.fn();
|
|
92
|
+
provider.image = vi.fn( model => ( { type: 'image', model } ) );
|
|
93
|
+
provider.imageModel = vi.fn( model => ( { type: 'imageModel', model } ) );
|
|
94
|
+
getProvider.mockReturnValue( provider );
|
|
351
95
|
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
96
|
+
const result = loadImageModel( {
|
|
97
|
+
config: {
|
|
98
|
+
provider: 'custom',
|
|
99
|
+
model: 'image-v1'
|
|
100
|
+
}
|
|
356
101
|
} );
|
|
357
102
|
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
provider: 'openai',
|
|
362
|
-
tools: {
|
|
363
|
-
webSearch: { searchContextSize: 'high' }
|
|
364
|
-
}
|
|
365
|
-
}
|
|
366
|
-
} );
|
|
367
|
-
|
|
368
|
-
expect( result.webSearch.config.searchContextSize ).toBe( 'high' );
|
|
369
|
-
} );
|
|
370
|
-
|
|
371
|
-
it( 'loads webSearch with filters.allowedDomains array', () => {
|
|
372
|
-
const result = loadTools( {
|
|
373
|
-
config: {
|
|
374
|
-
provider: 'openai',
|
|
375
|
-
tools: {
|
|
376
|
-
webSearch: {
|
|
377
|
-
filters: {
|
|
378
|
-
allowedDomains: [ 'wikipedia.org', 'github.com' ]
|
|
379
|
-
}
|
|
380
|
-
}
|
|
381
|
-
}
|
|
382
|
-
}
|
|
383
|
-
} );
|
|
384
|
-
|
|
385
|
-
expect( result.webSearch.config.filters.allowedDomains ).toEqual( [
|
|
386
|
-
'wikipedia.org',
|
|
387
|
-
'github.com'
|
|
388
|
-
] );
|
|
389
|
-
} );
|
|
390
|
-
|
|
391
|
-
it( 'loads webSearch with userLocation object', () => {
|
|
392
|
-
const result = loadTools( {
|
|
393
|
-
config: {
|
|
394
|
-
provider: 'openai',
|
|
395
|
-
tools: {
|
|
396
|
-
webSearch: {
|
|
397
|
-
userLocation: {
|
|
398
|
-
type: 'approximate',
|
|
399
|
-
country: 'US',
|
|
400
|
-
city: 'San Francisco'
|
|
401
|
-
}
|
|
402
|
-
}
|
|
403
|
-
}
|
|
404
|
-
}
|
|
405
|
-
} );
|
|
406
|
-
|
|
407
|
-
expect( result.webSearch.config.userLocation ).toEqual( {
|
|
408
|
-
type: 'approximate',
|
|
409
|
-
country: 'US',
|
|
410
|
-
city: 'San Francisco'
|
|
411
|
-
} );
|
|
412
|
-
} );
|
|
413
|
-
|
|
414
|
-
it( 'loads webSearch with all config options combined', () => {
|
|
415
|
-
const result = loadTools( {
|
|
416
|
-
config: {
|
|
417
|
-
provider: 'openai',
|
|
418
|
-
tools: {
|
|
419
|
-
webSearch: {
|
|
420
|
-
searchContextSize: 'high',
|
|
421
|
-
filters: {
|
|
422
|
-
allowedDomains: [ 'wikipedia.org' ]
|
|
423
|
-
},
|
|
424
|
-
userLocation: {
|
|
425
|
-
type: 'approximate',
|
|
426
|
-
country: 'US'
|
|
427
|
-
}
|
|
428
|
-
}
|
|
429
|
-
}
|
|
430
|
-
}
|
|
431
|
-
} );
|
|
432
|
-
|
|
433
|
-
expect( result.webSearch.config.searchContextSize ).toBe( 'high' );
|
|
434
|
-
expect( result.webSearch.config.filters.allowedDomains ).toHaveLength( 1 );
|
|
435
|
-
expect( result.webSearch.config.userLocation.country ).toBe( 'US' );
|
|
436
|
-
} );
|
|
103
|
+
expect( provider.image ).toHaveBeenCalledWith( 'image-v1' );
|
|
104
|
+
expect( provider.imageModel ).not.toHaveBeenCalled();
|
|
105
|
+
expect( result.type ).toBe( 'image' );
|
|
437
106
|
} );
|
|
438
107
|
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
it( 'loads webSearch_20250305 with empty config', () => {
|
|
442
|
-
const result = loadTools( {
|
|
443
|
-
config: {
|
|
444
|
-
provider: 'anthropic',
|
|
445
|
-
tools: { webSearch_20250305: {} }
|
|
446
|
-
}
|
|
447
|
-
} );
|
|
448
|
-
|
|
449
|
-
expect( result.webSearch_20250305 ).toEqual( {
|
|
450
|
-
type: 'webSearch_20250305',
|
|
451
|
-
config: {}
|
|
452
|
-
} );
|
|
453
|
-
} );
|
|
454
|
-
|
|
455
|
-
it( 'loads webSearch_20250305 with maxUses number', () => {
|
|
456
|
-
const result = loadTools( {
|
|
457
|
-
config: {
|
|
458
|
-
provider: 'anthropic',
|
|
459
|
-
tools: {
|
|
460
|
-
webSearch_20250305: { maxUses: 3 }
|
|
461
|
-
}
|
|
462
|
-
}
|
|
463
|
-
} );
|
|
464
|
-
|
|
465
|
-
expect( result.webSearch_20250305.config.maxUses ).toBe( 3 );
|
|
466
|
-
} );
|
|
467
|
-
|
|
468
|
-
it( 'loads webSearch_20250305 with allowedDomains and blockedDomains', () => {
|
|
469
|
-
const result = loadTools( {
|
|
470
|
-
config: {
|
|
471
|
-
provider: 'anthropic',
|
|
472
|
-
tools: {
|
|
473
|
-
webSearch_20250305: {
|
|
474
|
-
allowedDomains: [ 'reuters.com', 'bbc.com' ],
|
|
475
|
-
blockedDomains: [ 'tabloid.com' ]
|
|
476
|
-
}
|
|
477
|
-
}
|
|
478
|
-
}
|
|
479
|
-
} );
|
|
480
|
-
|
|
481
|
-
expect( result.webSearch_20250305.config.allowedDomains ).toEqual( [
|
|
482
|
-
'reuters.com',
|
|
483
|
-
'bbc.com'
|
|
484
|
-
] );
|
|
485
|
-
expect( result.webSearch_20250305.config.blockedDomains ).toEqual( [ 'tabloid.com' ] );
|
|
486
|
-
} );
|
|
108
|
+
it( 'throws a clear error when the provider does not support image models', () => {
|
|
109
|
+
getProvider.mockReturnValue( vi.fn() );
|
|
487
110
|
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
userLocation: {
|
|
495
|
-
type: 'approximate',
|
|
496
|
-
country: 'GB',
|
|
497
|
-
city: 'London',
|
|
498
|
-
timezone: 'Europe/London'
|
|
499
|
-
}
|
|
500
|
-
}
|
|
501
|
-
}
|
|
502
|
-
}
|
|
503
|
-
} );
|
|
504
|
-
|
|
505
|
-
expect( result.webSearch_20250305.config.userLocation.city ).toBe( 'London' );
|
|
506
|
-
expect( result.webSearch_20250305.config.userLocation.timezone ).toBe( 'Europe/London' );
|
|
507
|
-
} );
|
|
508
|
-
|
|
509
|
-
it( 'loads bash_20241022 and bash_20250124 tools', () => {
|
|
510
|
-
const result = loadTools( {
|
|
511
|
-
config: {
|
|
512
|
-
provider: 'anthropic',
|
|
513
|
-
tools: {
|
|
514
|
-
bash_20241022: {},
|
|
515
|
-
bash_20250124: {}
|
|
516
|
-
}
|
|
517
|
-
}
|
|
518
|
-
} );
|
|
519
|
-
|
|
520
|
-
expect( result.bash_20241022.type ).toBe( 'bash_20241022' );
|
|
521
|
-
expect( result.bash_20250124.type ).toBe( 'bash_20250124' );
|
|
522
|
-
} );
|
|
523
|
-
|
|
524
|
-
it( 'loads codeExecution_20250522 and codeExecution_20250825 tools', () => {
|
|
525
|
-
const result = loadTools( {
|
|
526
|
-
config: {
|
|
527
|
-
provider: 'anthropic',
|
|
528
|
-
tools: {
|
|
529
|
-
codeExecution_20250522: {},
|
|
530
|
-
codeExecution_20250825: {}
|
|
531
|
-
}
|
|
532
|
-
}
|
|
533
|
-
} );
|
|
534
|
-
|
|
535
|
-
expect( result.codeExecution_20250522.type ).toBe( 'codeExecution_20250522' );
|
|
536
|
-
expect( result.codeExecution_20250825.type ).toBe( 'codeExecution_20250825' );
|
|
537
|
-
} );
|
|
111
|
+
expect( () => loadImageModel( {
|
|
112
|
+
config: {
|
|
113
|
+
provider: 'azure',
|
|
114
|
+
model: 'gpt-image-1'
|
|
115
|
+
}
|
|
116
|
+
} ) ).toThrow( 'Provider "azure" does not support image models.' );
|
|
538
117
|
} );
|
|
118
|
+
} );
|
|
539
119
|
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
}
|
|
548
|
-
} );
|
|
549
|
-
|
|
550
|
-
expect( result.bash_20241022 ).toEqual( {
|
|
551
|
-
type: 'bash_20241022',
|
|
552
|
-
config: {}
|
|
553
|
-
} );
|
|
554
|
-
} );
|
|
555
|
-
|
|
556
|
-
it( 'loads textEditor_20241022 and textEditor_20250429 tools', () => {
|
|
557
|
-
const result = loadTools( {
|
|
558
|
-
config: {
|
|
559
|
-
provider: 'bedrock',
|
|
560
|
-
tools: {
|
|
561
|
-
textEditor_20241022: {},
|
|
562
|
-
textEditor_20250429: {}
|
|
563
|
-
}
|
|
564
|
-
}
|
|
565
|
-
} );
|
|
566
|
-
|
|
567
|
-
expect( result.textEditor_20241022.type ).toBe( 'textEditor_20241022' );
|
|
568
|
-
expect( result.textEditor_20250429.type ).toBe( 'textEditor_20250429' );
|
|
569
|
-
} );
|
|
570
|
-
|
|
571
|
-
it( 'loads computer_20241022 with config', () => {
|
|
572
|
-
const result = loadTools( {
|
|
573
|
-
config: {
|
|
574
|
-
provider: 'bedrock',
|
|
575
|
-
tools: {
|
|
576
|
-
computer_20241022: { displayWidthPx: 1024, displayHeightPx: 768 }
|
|
577
|
-
}
|
|
578
|
-
}
|
|
579
|
-
} );
|
|
580
|
-
|
|
581
|
-
expect( result.computer_20241022.config ).toEqual( {
|
|
582
|
-
displayWidthPx: 1024,
|
|
583
|
-
displayHeightPx: 768
|
|
584
|
-
} );
|
|
120
|
+
describe( 'loadTools', () => {
|
|
121
|
+
it( 'returns null and does not load the provider when no tools are configured', () => {
|
|
122
|
+
const result = loadTools( {
|
|
123
|
+
config: {
|
|
124
|
+
provider: 'vertex',
|
|
125
|
+
model: 'gemini-2.0-flash'
|
|
126
|
+
}
|
|
585
127
|
} );
|
|
586
128
|
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
config: {
|
|
590
|
-
provider: 'bedrock',
|
|
591
|
-
tools: {
|
|
592
|
-
bash_20241022: {},
|
|
593
|
-
textEditor_20250429: {},
|
|
594
|
-
computer_20241022: { displayWidthPx: 1920, displayHeightPx: 1080 }
|
|
595
|
-
}
|
|
596
|
-
}
|
|
597
|
-
} );
|
|
598
|
-
|
|
599
|
-
expect( Object.keys( result ) ).toEqual( [ 'bash_20241022', 'textEditor_20250429', 'computer_20241022' ] );
|
|
600
|
-
expect( result.bash_20241022.type ).toBe( 'bash_20241022' );
|
|
601
|
-
expect( result.textEditor_20250429.type ).toBe( 'textEditor_20250429' );
|
|
602
|
-
expect( result.computer_20241022.type ).toBe( 'computer_20241022' );
|
|
603
|
-
} );
|
|
129
|
+
expect( result ).toBeNull();
|
|
130
|
+
expect( getProvider ).not.toHaveBeenCalled();
|
|
604
131
|
} );
|
|
605
132
|
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
tools: [ 'googleSearch', 'urlContext' ]
|
|
613
|
-
}
|
|
614
|
-
} ) ).toThrow( /Invalid tools prompt config/ );
|
|
615
|
-
} );
|
|
616
|
-
|
|
617
|
-
it( 'throws error for string format', () => {
|
|
618
|
-
expect( () => loadTools( {
|
|
619
|
-
config: {
|
|
620
|
-
provider: 'vertex',
|
|
621
|
-
tools: 'googleSearch'
|
|
622
|
-
}
|
|
623
|
-
} ) ).toThrow( /Invalid tools prompt config/ );
|
|
624
|
-
} );
|
|
625
|
-
|
|
626
|
-
it( 'throws error for number format', () => {
|
|
627
|
-
expect( () => loadTools( {
|
|
628
|
-
config: {
|
|
629
|
-
provider: 'vertex',
|
|
630
|
-
tools: 123
|
|
631
|
-
}
|
|
632
|
-
} ) ).toThrow( /Invalid tools prompt config/ );
|
|
633
|
-
} );
|
|
634
|
-
|
|
635
|
-
it( 'throws error for provider without tools support', () => {
|
|
636
|
-
expect( () => loadTools( {
|
|
637
|
-
config: {
|
|
638
|
-
provider: 'azure',
|
|
639
|
-
tools: { someTool: {} }
|
|
640
|
-
}
|
|
641
|
-
} ) ).toThrow( 'does not support provider-specific tools' );
|
|
642
|
-
} );
|
|
643
|
-
|
|
644
|
-
it( 'throws error for unknown tool on Vertex with dynamic tool listing', () => {
|
|
645
|
-
expect( () => loadTools( {
|
|
646
|
-
config: {
|
|
647
|
-
provider: 'vertex',
|
|
648
|
-
tools: { unknownTool: {} }
|
|
649
|
-
}
|
|
650
|
-
} ) ).toThrow( /Unknown tool "unknownTool" for provider "vertex".*Available tools:/ );
|
|
651
|
-
} );
|
|
652
|
-
|
|
653
|
-
it( 'throws error for unknown tool on OpenAI with dynamic tool listing', () => {
|
|
654
|
-
expect( () => loadTools( {
|
|
655
|
-
config: {
|
|
656
|
-
provider: 'openai',
|
|
657
|
-
tools: { googleSearch: {} }
|
|
658
|
-
}
|
|
659
|
-
} ) ).toThrow( /Unknown tool "googleSearch" for provider "openai".*Available tools:/ );
|
|
133
|
+
it( 'returns null and does not load the provider when tools config is empty', () => {
|
|
134
|
+
const result = loadTools( {
|
|
135
|
+
config: {
|
|
136
|
+
provider: 'vertex',
|
|
137
|
+
tools: {}
|
|
138
|
+
}
|
|
660
139
|
} );
|
|
661
140
|
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
provider: 'anthropic',
|
|
666
|
-
tools: { googleSearch: {} }
|
|
667
|
-
}
|
|
668
|
-
} ) ).toThrow( /Unknown tool "googleSearch" for provider "anthropic".*Available tools:/ );
|
|
669
|
-
} );
|
|
141
|
+
expect( result ).toBeNull();
|
|
142
|
+
expect( getProvider ).not.toHaveBeenCalled();
|
|
143
|
+
} );
|
|
670
144
|
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
config: {
|
|
674
|
-
provider: 'vertex',
|
|
675
|
-
tools: { googleSearch: null }
|
|
676
|
-
}
|
|
677
|
-
} ) ).toThrow( /Invalid tools prompt config.*expected record, received null/s );
|
|
678
|
-
} );
|
|
145
|
+
it( 'throws when the provider has no tools object', () => {
|
|
146
|
+
getProvider.mockReturnValue( vi.fn() );
|
|
679
147
|
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
148
|
+
expect( () => loadTools( {
|
|
149
|
+
config: {
|
|
150
|
+
provider: 'azure',
|
|
151
|
+
tools: {
|
|
152
|
+
webSearch: {}
|
|
685
153
|
}
|
|
686
|
-
}
|
|
687
|
-
} );
|
|
688
|
-
|
|
689
|
-
it( 'throws error for unknown tool on Bedrock with dynamic tool listing', () => {
|
|
690
|
-
expect( () => loadTools( {
|
|
691
|
-
config: { provider: 'bedrock', tools: { webSearch: {} } }
|
|
692
|
-
} ) ).toThrow( /Unknown tool "webSearch" for provider "bedrock".*Available tools:/ );
|
|
693
|
-
} );
|
|
154
|
+
}
|
|
155
|
+
} ) ).toThrow( 'Provider "azure" does not support provider-specific tools.' );
|
|
694
156
|
} );
|
|
695
157
|
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
158
|
+
it( 'loads a single provider tool with config', () => {
|
|
159
|
+
const googleSearch = vi.fn( config => ( { type: 'googleSearch', config } ) );
|
|
160
|
+
const provider = {
|
|
161
|
+
tools: {
|
|
162
|
+
googleSearch
|
|
163
|
+
}
|
|
164
|
+
};
|
|
165
|
+
getProvider.mockReturnValue( provider );
|
|
166
|
+
|
|
167
|
+
const result = loadTools( {
|
|
168
|
+
config: {
|
|
701
169
|
provider: 'vertex',
|
|
702
|
-
model: 'gemini-2.0-flash',
|
|
703
170
|
tools: {
|
|
704
171
|
googleSearch: {
|
|
705
172
|
mode: 'MODE_DYNAMIC',
|
|
706
173
|
dynamicThreshold: 0.8
|
|
707
174
|
}
|
|
708
175
|
}
|
|
709
|
-
}
|
|
710
|
-
|
|
711
|
-
const result = loadTools( { config: renderedConfig } );
|
|
712
|
-
|
|
713
|
-
expect( result.googleSearch.config ).toEqual( {
|
|
714
|
-
mode: 'MODE_DYNAMIC',
|
|
715
|
-
dynamicThreshold: 0.8
|
|
716
|
-
} );
|
|
176
|
+
}
|
|
717
177
|
} );
|
|
718
178
|
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
customField: 'value'
|
|
724
|
-
};
|
|
725
|
-
|
|
726
|
-
const result = loadTools( {
|
|
727
|
-
config: {
|
|
728
|
-
provider: 'vertex',
|
|
729
|
-
tools: { googleSearch: customConfig }
|
|
730
|
-
}
|
|
731
|
-
} );
|
|
732
|
-
|
|
733
|
-
// The mock returns { type, config }, so we can verify config was passed through
|
|
734
|
-
expect( result.googleSearch.config ).toEqual( customConfig );
|
|
179
|
+
expect( getProvider ).toHaveBeenCalledWith( 'vertex' );
|
|
180
|
+
expect( googleSearch ).toHaveBeenCalledWith( {
|
|
181
|
+
mode: 'MODE_DYNAMIC',
|
|
182
|
+
dynamicThreshold: 0.8
|
|
735
183
|
} );
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
184
|
+
expect( result ).toEqual( {
|
|
185
|
+
googleSearch: {
|
|
186
|
+
type: 'googleSearch',
|
|
739
187
|
config: {
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
webSearch: {
|
|
743
|
-
searchContextSize: 'high',
|
|
744
|
-
filters: {
|
|
745
|
-
allowedDomains: [ 'example.com' ],
|
|
746
|
-
blockedDomains: [ 'spam.com' ]
|
|
747
|
-
},
|
|
748
|
-
userLocation: {
|
|
749
|
-
type: 'approximate',
|
|
750
|
-
country: 'US',
|
|
751
|
-
city: 'Seattle',
|
|
752
|
-
region: 'WA'
|
|
753
|
-
}
|
|
754
|
-
}
|
|
755
|
-
}
|
|
188
|
+
mode: 'MODE_DYNAMIC',
|
|
189
|
+
dynamicThreshold: 0.8
|
|
756
190
|
}
|
|
757
|
-
}
|
|
758
|
-
|
|
759
|
-
expect( result.webSearch.config.filters ).toBeDefined();
|
|
760
|
-
expect( result.webSearch.config.userLocation ).toBeDefined();
|
|
761
|
-
expect( result.webSearch.config.filters.allowedDomains ).toEqual( [ 'example.com' ] );
|
|
191
|
+
}
|
|
762
192
|
} );
|
|
763
193
|
} );
|
|
764
|
-
} );
|
|
765
|
-
|
|
766
|
-
describe( 'registerProvider', () => {
|
|
767
|
-
afterEach( () => {
|
|
768
|
-
for ( const key of Object.keys( providers ) ) {
|
|
769
|
-
delete providers[key];
|
|
770
|
-
}
|
|
771
|
-
Object.assign( providers, builtInProviders );
|
|
772
|
-
} );
|
|
773
194
|
|
|
774
|
-
it( '
|
|
775
|
-
const
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
it( 'overrides a built-in provider', () => {
|
|
785
|
-
const overrideOpenai = vi.fn( model => `override:${model}` );
|
|
786
|
-
registerProvider( 'openai', overrideOpenai );
|
|
787
|
-
|
|
788
|
-
const result = loadTextModel( { config: { provider: 'openai', model: 'gpt-custom' } } );
|
|
789
|
-
|
|
790
|
-
expect( result ).toBe( 'override:gpt-custom' );
|
|
791
|
-
} );
|
|
792
|
-
|
|
793
|
-
it( 'throws when name is empty string', () => {
|
|
794
|
-
expect( () => registerProvider( '', vi.fn() ) ).toThrow( 'non-empty string' );
|
|
795
|
-
} );
|
|
796
|
-
|
|
797
|
-
it( 'throws when name is not a string', () => {
|
|
798
|
-
expect( () => registerProvider( 123, vi.fn() ) ).toThrow( 'expected string, received number' );
|
|
799
|
-
} );
|
|
800
|
-
|
|
801
|
-
it( 'throws when providerFn is not a function', () => {
|
|
802
|
-
expect( () => registerProvider( 'bad', 'not-a-function' ) ).toThrow( 'expected function, received string' );
|
|
803
|
-
} );
|
|
195
|
+
it( 'loads multiple provider tools', () => {
|
|
196
|
+
const googleSearch = vi.fn( config => ( { type: 'googleSearch', config } ) );
|
|
197
|
+
const urlContext = vi.fn( config => ( { type: 'urlContext', config } ) );
|
|
198
|
+
getProvider.mockReturnValue( {
|
|
199
|
+
tools: {
|
|
200
|
+
googleSearch,
|
|
201
|
+
urlContext
|
|
202
|
+
}
|
|
203
|
+
} );
|
|
804
204
|
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
205
|
+
const result = loadTools( {
|
|
206
|
+
config: {
|
|
207
|
+
provider: 'vertex',
|
|
208
|
+
tools: {
|
|
209
|
+
googleSearch: {
|
|
210
|
+
mode: 'MODE_DYNAMIC'
|
|
211
|
+
},
|
|
212
|
+
urlContext: {}
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
} );
|
|
809
216
|
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
Object.assign( providers, builtInProviders );
|
|
217
|
+
expect( Object.keys( result ) ).toEqual( [ 'googleSearch', 'urlContext' ] );
|
|
218
|
+
expect( googleSearch ).toHaveBeenCalledWith( { mode: 'MODE_DYNAMIC' } );
|
|
219
|
+
expect( urlContext ).toHaveBeenCalledWith( {} );
|
|
220
|
+
expect( result.googleSearch.type ).toBe( 'googleSearch' );
|
|
221
|
+
expect( result.urlContext.type ).toBe( 'urlContext' );
|
|
816
222
|
} );
|
|
817
223
|
|
|
818
|
-
it( '
|
|
819
|
-
|
|
224
|
+
it( 'throws when a configured tool is not supported by the provider', () => {
|
|
225
|
+
getProvider.mockReturnValue( {
|
|
226
|
+
tools: {
|
|
227
|
+
googleSearch: vi.fn(),
|
|
228
|
+
urlContext: vi.fn()
|
|
229
|
+
}
|
|
230
|
+
} );
|
|
820
231
|
|
|
821
|
-
expect(
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
232
|
+
expect( () => loadTools( {
|
|
233
|
+
config: {
|
|
234
|
+
provider: 'vertex',
|
|
235
|
+
tools: {
|
|
236
|
+
unknownTool: {}
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
} ) ).toThrow( 'Invalid tool(s) unknownTool for provider "vertex". Available: googleSearch, urlContext.' );
|
|
827
240
|
} );
|
|
828
241
|
|
|
829
|
-
it( '
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
242
|
+
it( 'reports all unsupported configured tools', () => {
|
|
243
|
+
getProvider.mockReturnValue( {
|
|
244
|
+
tools: {
|
|
245
|
+
googleSearch: vi.fn()
|
|
246
|
+
}
|
|
247
|
+
} );
|
|
833
248
|
|
|
834
|
-
expect(
|
|
249
|
+
expect( () => loadTools( {
|
|
250
|
+
config: {
|
|
251
|
+
provider: 'vertex',
|
|
252
|
+
tools: {
|
|
253
|
+
unknownTool: {},
|
|
254
|
+
anotherUnknownTool: {}
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
} ) ).toThrow( 'Invalid tool(s) unknownTool, anotherUnknownTool for provider "vertex". Available: googleSearch.' );
|
|
835
258
|
} );
|
|
836
259
|
} );
|