@outputai/llm 0.11.1-next.da26845.0 → 0.12.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 +7 -3
- package/src/agent.js +7 -2
- package/src/generate.js +1 -0
- package/src/index.d.ts +14 -6
- package/src/utils/wrap.js +35 -2
- package/src/agent.spec.js +0 -595
- package/src/ai_provider.spec.js +0 -217
- package/src/ai_sdk_options.spec.js +0 -390
- package/src/fixtures/image_response_v7_google_vertex.js +0 -72
- package/src/fixtures/image_response_v7_openai.js +0 -77
- package/src/fixtures/models_api_light.json +0 -675
- package/src/fixtures/models_api_v7_subset.json +0 -259
- package/src/fixtures/stream_response_v7_anthropic.js +0 -389
- package/src/fixtures/stream_response_v7_google_vertex.js +0 -462
- package/src/fixtures/stream_response_v7_openai.js +0 -353
- package/src/fixtures/stream_response_v7_perplexity.js +0 -671
- package/src/fixtures/text_response_v7_anthropic.js +0 -359
- package/src/fixtures/text_response_v7_anthropic_cache.js +0 -719
- package/src/fixtures/text_response_v7_google_vertex.js +0 -435
- package/src/fixtures/text_response_v7_openai.js +0 -347
- package/src/fixtures/text_response_v7_perplexity.js +0 -764
- package/src/generate.spec.js +0 -525
- package/src/prompt/content.spec.js +0 -208
- package/src/prompt/interpolations.spec.js +0 -109
- package/src/prompt/loader.full.spec.js +0 -309
- package/src/prompt/loader.spec.js +0 -258
- package/src/prompt/markup/attributes.spec.js +0 -132
- package/src/prompt/markup/nodes.spec.js +0 -333
- package/src/prompt/markup/tokenizer.spec.js +0 -149
- package/src/prompt/markup/tokens.spec.js +0 -250
- package/src/prompt/validations.spec.js +0 -812
- package/src/utils/cost.full.spec.js +0 -272
- package/src/utils/cost.spec.js +0 -290
- package/src/utils/error_handler.spec.js +0 -230
- package/src/utils/file.spec.js +0 -89
- package/src/utils/image.spec.js +0 -20
- package/src/utils/legacy_cost_attribute.spec.js +0 -278
- package/src/utils/models.spec.js +0 -119
- package/src/utils/models_pricing.spec.js +0 -162
- package/src/utils/skills.spec.js +0 -168
- package/src/utils/sources.spec.js +0 -122
- package/src/utils/stream.spec.js +0 -55
- package/src/utils/tools.spec.js +0 -167
- package/src/utils/usage.full.spec.js +0 -215
- package/src/utils/usage.spec.js +0 -246
- package/src/utils/wrap.spec.js +0 -467
- package/src/validations.spec.js +0 -631
package/src/validations.spec.js
DELETED
|
@@ -1,631 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect } from 'vitest';
|
|
2
|
-
import { ValidationError } from '@outputai/core';
|
|
3
|
-
import {
|
|
4
|
-
parseGenerateTextArgs,
|
|
5
|
-
parseGenerateTextWithStreamingArgs,
|
|
6
|
-
parseStreamTextArgs,
|
|
7
|
-
parseGenerateImageArgs,
|
|
8
|
-
parseAgentArgs,
|
|
9
|
-
parseAgentGenerateArgs,
|
|
10
|
-
parseAgentGenerateWithStreamingArgs,
|
|
11
|
-
parseAgentStreamArgs
|
|
12
|
-
} from './validations.js';
|
|
13
|
-
|
|
14
|
-
const makeOutput = () => ( { name: 'object' } );
|
|
15
|
-
|
|
16
|
-
const searchTool = { description: 'Search' };
|
|
17
|
-
const stopWhen = () => false;
|
|
18
|
-
const promptObject = {
|
|
19
|
-
name: 'custom',
|
|
20
|
-
config: {
|
|
21
|
-
provider: 'openai',
|
|
22
|
-
model: 'gpt-4o',
|
|
23
|
-
maxSteps: 10,
|
|
24
|
-
skills: []
|
|
25
|
-
},
|
|
26
|
-
messages: [ { role: 'user', content: 'Hello' } ],
|
|
27
|
-
instructions: null,
|
|
28
|
-
fileDir: '/prompts',
|
|
29
|
-
variables: {}
|
|
30
|
-
};
|
|
31
|
-
|
|
32
|
-
describe( 'prompt arguments', () => {
|
|
33
|
-
it.each( [
|
|
34
|
-
[ 'generateText', parseGenerateTextArgs ],
|
|
35
|
-
[ 'generateTextWithStreaming', parseGenerateTextWithStreamingArgs ],
|
|
36
|
-
[ 'streamText', parseStreamTextArgs ],
|
|
37
|
-
[ 'generateImage', parseGenerateImageArgs ],
|
|
38
|
-
[ 'Agent', parseAgentArgs ]
|
|
39
|
-
] )( 'normalizes a prompt object for %s', ( _, parse ) => {
|
|
40
|
-
expect( parse( { prompt: promptObject } ) ).toEqual( {
|
|
41
|
-
promptObject
|
|
42
|
-
} );
|
|
43
|
-
} );
|
|
44
|
-
|
|
45
|
-
it.each( [ 'promptDir', 'variables' ] )( 'rejects %s with a prompt object', field => {
|
|
46
|
-
expect( () => parseGenerateTextArgs( {
|
|
47
|
-
prompt: promptObject,
|
|
48
|
-
[field]: field === 'promptDir' ? '/other' : { topic: 'other' }
|
|
49
|
-
} ) ).toThrow( `${field} cannot be used when prompt is an object` );
|
|
50
|
-
} );
|
|
51
|
-
|
|
52
|
-
it( 'rejects prompt file arguments with an Agent prompt object', () => {
|
|
53
|
-
expect( () => parseAgentArgs( {
|
|
54
|
-
prompt: promptObject,
|
|
55
|
-
promptDir: '/other'
|
|
56
|
-
} ) ).toThrow( 'promptDir cannot be used when prompt is an object' );
|
|
57
|
-
} );
|
|
58
|
-
|
|
59
|
-
it( 'reports specific validation issues for an invalid prompt object', () => {
|
|
60
|
-
expect( () => parseGenerateTextArgs( {
|
|
61
|
-
prompt: {
|
|
62
|
-
...promptObject,
|
|
63
|
-
config: {
|
|
64
|
-
provider: 'openai'
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
} ) ).toThrow( /prompt\.config\.model/ );
|
|
68
|
-
} );
|
|
69
|
-
|
|
70
|
-
it.each( [
|
|
71
|
-
[ 'missing', {} ],
|
|
72
|
-
[ 'numeric', { prompt: 123, variables: { topic: 'testing' } } ]
|
|
73
|
-
] )( 'keeps filename-shaped errors for a %s prompt', ( _, args ) => {
|
|
74
|
-
const error = { message: '' };
|
|
75
|
-
try {
|
|
76
|
-
parseGenerateTextArgs( args );
|
|
77
|
-
} catch ( e ) {
|
|
78
|
-
error.message = e.message;
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
expect( error.message ).toContain( 'expected string' );
|
|
82
|
-
expect( error.message ).not.toContain( 'variables cannot be used when prompt is an object' );
|
|
83
|
-
} );
|
|
84
|
-
} );
|
|
85
|
-
|
|
86
|
-
describe( 'parseGenerateTextArgs', () => {
|
|
87
|
-
it( 'renames prompt to promptFile and keeps allowlisted leftovers', () => {
|
|
88
|
-
const abortSignal = AbortSignal.abort();
|
|
89
|
-
const tools = { search: searchTool };
|
|
90
|
-
const output = makeOutput();
|
|
91
|
-
|
|
92
|
-
expect( parseGenerateTextArgs( {
|
|
93
|
-
prompt: 'summary@v1',
|
|
94
|
-
variables: { topic: 'testing', count: 2, draft: false },
|
|
95
|
-
promptDir: '/prompts',
|
|
96
|
-
tools,
|
|
97
|
-
output,
|
|
98
|
-
toolChoice: { type: 'tool', toolName: 'search' },
|
|
99
|
-
stopWhen,
|
|
100
|
-
abortSignal
|
|
101
|
-
} ) ).toEqual( {
|
|
102
|
-
promptFile: 'summary@v1',
|
|
103
|
-
variables: { topic: 'testing', count: 2, draft: false },
|
|
104
|
-
promptDir: '/prompts',
|
|
105
|
-
tools,
|
|
106
|
-
output,
|
|
107
|
-
toolChoice: { type: 'tool', toolName: 'search' },
|
|
108
|
-
stopWhen,
|
|
109
|
-
abortSignal
|
|
110
|
-
} );
|
|
111
|
-
} );
|
|
112
|
-
|
|
113
|
-
it( 'accepts an array of stopWhen functions', () => {
|
|
114
|
-
const parsed = parseGenerateTextArgs( {
|
|
115
|
-
prompt: 'summary@v1',
|
|
116
|
-
stopWhen: [ stopWhen ]
|
|
117
|
-
} );
|
|
118
|
-
|
|
119
|
-
expect( parsed.stopWhen ).toEqual( [ stopWhen ] );
|
|
120
|
-
} );
|
|
121
|
-
|
|
122
|
-
it( 'throws ValidationError with generateText prefix for invalid args', () => {
|
|
123
|
-
expect( () => parseGenerateTextArgs( {
|
|
124
|
-
variables: { topic: 'testing' }
|
|
125
|
-
} ) ).toThrow( ValidationError );
|
|
126
|
-
|
|
127
|
-
expect( () => parseGenerateTextArgs( {
|
|
128
|
-
prompt: 123
|
|
129
|
-
} ) ).toThrow( /Invalid generateText\(\) arguments/ );
|
|
130
|
-
|
|
131
|
-
expect( () => parseGenerateTextArgs( {
|
|
132
|
-
prompt: 'summary@v1',
|
|
133
|
-
promptDir: ''
|
|
134
|
-
} ) ).toThrow( ValidationError );
|
|
135
|
-
|
|
136
|
-
expect( () => parseGenerateTextArgs( {
|
|
137
|
-
prompt: 'summary@v1',
|
|
138
|
-
abortSignal: {}
|
|
139
|
-
} ) ).toThrow( ValidationError );
|
|
140
|
-
} );
|
|
141
|
-
|
|
142
|
-
it( 'does not keep prompt-owned call arguments on the parsed result', () => {
|
|
143
|
-
expect( () => parseGenerateTextArgs( {
|
|
144
|
-
prompt: 'summary@v1',
|
|
145
|
-
skills: [ { name: 'style', description: 'Style', instructions: '# Style' } ]
|
|
146
|
-
} ) ).toThrow( /skills must be set in the prompt file/ );
|
|
147
|
-
|
|
148
|
-
expect( () => parseGenerateTextArgs( {
|
|
149
|
-
prompt: 'summary@v1',
|
|
150
|
-
maxSteps: 4,
|
|
151
|
-
skills: [ './writer.md' ]
|
|
152
|
-
} ) ).toThrow( /must be set in the prompt file/ );
|
|
153
|
-
} );
|
|
154
|
-
|
|
155
|
-
it( 'throws ValidationError for unrecognized call arguments', () => {
|
|
156
|
-
expect( () => parseGenerateTextArgs( {
|
|
157
|
-
prompt: 'summary@v1',
|
|
158
|
-
temperature: 0.2
|
|
159
|
-
} ) ).toThrow( /unrecognized key/i );
|
|
160
|
-
|
|
161
|
-
expect( () => parseGenerateTextArgs( {
|
|
162
|
-
prompt: 'summary@v1',
|
|
163
|
-
maxRetries: 2
|
|
164
|
-
} ) ).toThrow( /unrecognized key/i );
|
|
165
|
-
|
|
166
|
-
expect( () => parseGenerateTextArgs( {
|
|
167
|
-
prompt: 'summary@v1',
|
|
168
|
-
onChunk: () => {}
|
|
169
|
-
} ) ).toThrow( /unrecognized key/i );
|
|
170
|
-
} );
|
|
171
|
-
|
|
172
|
-
it( 'accepts nested objects and arrays as variables', () => {
|
|
173
|
-
const variables = {
|
|
174
|
-
company: { name: 'Acme', industries: [ 'SaaS', 'AI' ] },
|
|
175
|
-
criteria: [ { name: 'Accuracy', required: true } ]
|
|
176
|
-
};
|
|
177
|
-
|
|
178
|
-
expect( parseGenerateTextArgs( {
|
|
179
|
-
prompt: 'summary@v1',
|
|
180
|
-
variables
|
|
181
|
-
} ).variables ).toEqual( variables );
|
|
182
|
-
} );
|
|
183
|
-
|
|
184
|
-
it( 'throws ValidationError for invalid allowlisted field shapes', () => {
|
|
185
|
-
expect( () => parseGenerateTextArgs( {
|
|
186
|
-
prompt: 'summary@v1',
|
|
187
|
-
variables: [ 'not a record' ]
|
|
188
|
-
} ) ).toThrow( ValidationError );
|
|
189
|
-
|
|
190
|
-
expect( () => parseGenerateTextArgs( {
|
|
191
|
-
prompt: 'summary@v1',
|
|
192
|
-
tools: { search: true }
|
|
193
|
-
} ) ).toThrow( ValidationError );
|
|
194
|
-
|
|
195
|
-
expect( () => parseGenerateTextArgs( {
|
|
196
|
-
prompt: 'summary@v1',
|
|
197
|
-
output: true
|
|
198
|
-
} ) ).toThrow( ValidationError );
|
|
199
|
-
|
|
200
|
-
expect( () => parseGenerateTextArgs( {
|
|
201
|
-
prompt: 'summary@v1',
|
|
202
|
-
stopWhen: { type: 'custom-stop' }
|
|
203
|
-
} ) ).toThrow( ValidationError );
|
|
204
|
-
|
|
205
|
-
expect( () => parseGenerateTextArgs( {
|
|
206
|
-
prompt: 'summary@v1',
|
|
207
|
-
toolChoice: 'maybe'
|
|
208
|
-
} ) ).toThrow( ValidationError );
|
|
209
|
-
} );
|
|
210
|
-
|
|
211
|
-
it( 'accepts opaque tools and output objects', () => {
|
|
212
|
-
expect( parseGenerateTextArgs( {
|
|
213
|
-
prompt: 'summary@v1',
|
|
214
|
-
tools: { search: { description: 'Search' } },
|
|
215
|
-
output: { type: 'object' }
|
|
216
|
-
} ) ).toMatchObject( {
|
|
217
|
-
tools: { search: { description: 'Search' } },
|
|
218
|
-
output: { type: 'object' }
|
|
219
|
-
} );
|
|
220
|
-
} );
|
|
221
|
-
|
|
222
|
-
it( 'preserves class-based tool and output identities', () => {
|
|
223
|
-
class TestTool {
|
|
224
|
-
#result = 'searched';
|
|
225
|
-
|
|
226
|
-
execute() {
|
|
227
|
-
return this.#result;
|
|
228
|
-
}
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
class TestOutput {
|
|
232
|
-
#result = 'parsed';
|
|
233
|
-
|
|
234
|
-
parseCompleteOutput() {
|
|
235
|
-
return this.#result;
|
|
236
|
-
}
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
const tool = new TestTool();
|
|
240
|
-
const output = new TestOutput();
|
|
241
|
-
const parsed = parseGenerateTextArgs( {
|
|
242
|
-
prompt: 'summary@v1',
|
|
243
|
-
tools: { search: tool },
|
|
244
|
-
output
|
|
245
|
-
} );
|
|
246
|
-
|
|
247
|
-
expect( parsed.tools.search ).toBe( tool );
|
|
248
|
-
expect( parsed.tools.search.execute() ).toBe( 'searched' );
|
|
249
|
-
expect( parsed.output ).toBe( output );
|
|
250
|
-
expect( parsed.output.parseCompleteOutput() ).toBe( 'parsed' );
|
|
251
|
-
} );
|
|
252
|
-
} );
|
|
253
|
-
|
|
254
|
-
describe( 'parseStreamTextArgs', () => {
|
|
255
|
-
it( 'keeps stream callbacks on the parsed result', () => {
|
|
256
|
-
const parsed = parseStreamTextArgs( {
|
|
257
|
-
prompt: 'summary@v1',
|
|
258
|
-
variables: { topic: 'testing' },
|
|
259
|
-
promptDir: '/prompts',
|
|
260
|
-
onEnd: () => {},
|
|
261
|
-
onError: () => {},
|
|
262
|
-
onChunk: () => {}
|
|
263
|
-
} );
|
|
264
|
-
|
|
265
|
-
expect( parsed.promptFile ).toBe( 'summary@v1' );
|
|
266
|
-
expect( parsed.variables ).toEqual( { topic: 'testing' } );
|
|
267
|
-
expect( parsed.promptDir ).toBe( '/prompts' );
|
|
268
|
-
expect( parsed.onEnd ).toBeTypeOf( 'function' );
|
|
269
|
-
expect( parsed.onError ).toBeTypeOf( 'function' );
|
|
270
|
-
expect( parsed.onChunk ).toBeTypeOf( 'function' );
|
|
271
|
-
} );
|
|
272
|
-
|
|
273
|
-
it( 'throws ValidationError with streamText prefix for invalid args', () => {
|
|
274
|
-
expect( () => parseStreamTextArgs( {} ) ).toThrow( ValidationError );
|
|
275
|
-
expect( () => parseStreamTextArgs( { prompt: null } ) ).toThrow( /Invalid streamText\(\) arguments/ );
|
|
276
|
-
|
|
277
|
-
expect( () => parseStreamTextArgs( {
|
|
278
|
-
prompt: 'summary@v1',
|
|
279
|
-
promptDir: ''
|
|
280
|
-
} ) ).toThrow( ValidationError );
|
|
281
|
-
} );
|
|
282
|
-
|
|
283
|
-
it( 'throws ValidationError for invalid callbacks', () => {
|
|
284
|
-
expect( () => parseStreamTextArgs( {
|
|
285
|
-
prompt: 'summary@v1',
|
|
286
|
-
onEnd: 'not a function'
|
|
287
|
-
} ) ).toThrow( ValidationError );
|
|
288
|
-
|
|
289
|
-
expect( () => parseStreamTextArgs( {
|
|
290
|
-
prompt: 'summary@v1',
|
|
291
|
-
onError: {}
|
|
292
|
-
} ) ).toThrow( ValidationError );
|
|
293
|
-
|
|
294
|
-
expect( () => parseStreamTextArgs( {
|
|
295
|
-
prompt: 'summary@v1',
|
|
296
|
-
onChunk: 'not a function'
|
|
297
|
-
} ) ).toThrow( ValidationError );
|
|
298
|
-
} );
|
|
299
|
-
|
|
300
|
-
it( 'throws ValidationError for unrecognized call arguments', () => {
|
|
301
|
-
expect( () => parseStreamTextArgs( {
|
|
302
|
-
prompt: 'summary@v1',
|
|
303
|
-
temperature: 0.2
|
|
304
|
-
} ) ).toThrow( /unrecognized key/i );
|
|
305
|
-
|
|
306
|
-
expect( () => parseStreamTextArgs( {
|
|
307
|
-
prompt: 'summary@v1',
|
|
308
|
-
skills: [ { name: 'style', description: 'Style', instructions: '# Style' } ]
|
|
309
|
-
} ) ).toThrow( /skills must be set in the prompt file/ );
|
|
310
|
-
|
|
311
|
-
expect( () => parseStreamTextArgs( {
|
|
312
|
-
prompt: 'summary@v1',
|
|
313
|
-
maxSteps: 1.5
|
|
314
|
-
} ) ).toThrow( /maxSteps must be set in the prompt file/ );
|
|
315
|
-
} );
|
|
316
|
-
} );
|
|
317
|
-
|
|
318
|
-
describe( 'parseGenerateTextWithStreamingArgs', () => {
|
|
319
|
-
it( 'keeps onChunk and omits streamText-only callbacks', () => {
|
|
320
|
-
const parsed = parseGenerateTextWithStreamingArgs( {
|
|
321
|
-
prompt: 'summary@v1',
|
|
322
|
-
onChunk: () => {}
|
|
323
|
-
} );
|
|
324
|
-
|
|
325
|
-
expect( parsed.promptFile ).toBe( 'summary@v1' );
|
|
326
|
-
expect( parsed.onChunk ).toBeTypeOf( 'function' );
|
|
327
|
-
|
|
328
|
-
expect( () => parseGenerateTextWithStreamingArgs( {
|
|
329
|
-
prompt: 'summary@v1',
|
|
330
|
-
onEnd: () => {}
|
|
331
|
-
} ) ).toThrow( /unrecognized key/i );
|
|
332
|
-
} );
|
|
333
|
-
|
|
334
|
-
it( 'uses the generateTextWithStreaming error prefix', () => {
|
|
335
|
-
expect( () => parseGenerateTextWithStreamingArgs( {
|
|
336
|
-
prompt: 'summary@v1',
|
|
337
|
-
maxSteps: 0
|
|
338
|
-
} ) ).toThrow( /Invalid generateTextWithStreaming\(\) arguments[\s\S]*maxSteps must be set in the prompt file/ );
|
|
339
|
-
} );
|
|
340
|
-
} );
|
|
341
|
-
|
|
342
|
-
describe( 'parseGenerateImageArgs', () => {
|
|
343
|
-
it( 'renames prompt to promptFile and keeps allowlisted leftovers', () => {
|
|
344
|
-
const images = [ Buffer.from( 'image-bytes' ) ];
|
|
345
|
-
const mask = Buffer.from( 'mask-bytes' );
|
|
346
|
-
const abortSignal = AbortSignal.abort();
|
|
347
|
-
|
|
348
|
-
expect( parseGenerateImageArgs( {
|
|
349
|
-
prompt: 'image@v1',
|
|
350
|
-
variables: { scene: 'race cars' },
|
|
351
|
-
promptDir: '/prompts',
|
|
352
|
-
images,
|
|
353
|
-
mask,
|
|
354
|
-
abortSignal
|
|
355
|
-
} ) ).toEqual( {
|
|
356
|
-
promptFile: 'image@v1',
|
|
357
|
-
variables: { scene: 'race cars' },
|
|
358
|
-
promptDir: '/prompts',
|
|
359
|
-
images,
|
|
360
|
-
mask,
|
|
361
|
-
abortSignal
|
|
362
|
-
} );
|
|
363
|
-
} );
|
|
364
|
-
|
|
365
|
-
it( 'accepts text-to-image args without images or mask', () => {
|
|
366
|
-
expect( parseGenerateImageArgs( {
|
|
367
|
-
prompt: 'image@v1',
|
|
368
|
-
variables: { topic: 'race cars' },
|
|
369
|
-
promptDir: '/prompts'
|
|
370
|
-
} ) ).toEqual( {
|
|
371
|
-
promptFile: 'image@v1',
|
|
372
|
-
variables: { topic: 'race cars' },
|
|
373
|
-
promptDir: '/prompts'
|
|
374
|
-
} );
|
|
375
|
-
} );
|
|
376
|
-
|
|
377
|
-
it( 'accepts all supported image input shapes', () => {
|
|
378
|
-
const buffer = Buffer.from( 'image-bytes' );
|
|
379
|
-
const uint8Array = new Uint8Array( [ 1, 2, 3 ] );
|
|
380
|
-
const arrayBuffer = new ArrayBuffer( 3 );
|
|
381
|
-
const paddedBase64 = 'aW1hZ2UtYnl0ZXM=';
|
|
382
|
-
const unpaddedBase64 = 'aW1hZ2U';
|
|
383
|
-
|
|
384
|
-
expect( () => parseGenerateImageArgs( {
|
|
385
|
-
prompt: 'image@v1',
|
|
386
|
-
images: [
|
|
387
|
-
buffer,
|
|
388
|
-
uint8Array,
|
|
389
|
-
arrayBuffer,
|
|
390
|
-
paddedBase64,
|
|
391
|
-
unpaddedBase64,
|
|
392
|
-
{ data: buffer, mediaType: 'image/png' },
|
|
393
|
-
{ data: uint8Array },
|
|
394
|
-
{ data: arrayBuffer, mediaType: 'image/jpeg' },
|
|
395
|
-
{ data: paddedBase64, mediaType: 'image/webp' }
|
|
396
|
-
],
|
|
397
|
-
mask: { data: Buffer.from( 'mask-bytes' ), mediaType: 'image/png' }
|
|
398
|
-
} ) ).not.toThrow();
|
|
399
|
-
} );
|
|
400
|
-
|
|
401
|
-
it( 'throws ValidationError for invalid image args', () => {
|
|
402
|
-
expect( () => parseGenerateImageArgs( {
|
|
403
|
-
prompt: ''
|
|
404
|
-
} ) ).toThrow( /Invalid generateImage\(\) arguments/ );
|
|
405
|
-
|
|
406
|
-
expect( () => parseGenerateImageArgs( {
|
|
407
|
-
prompt: 'image@v1',
|
|
408
|
-
images: []
|
|
409
|
-
} ) ).toThrow( ValidationError );
|
|
410
|
-
|
|
411
|
-
expect( () => parseGenerateImageArgs( {
|
|
412
|
-
prompt: 'image@v1',
|
|
413
|
-
images: [ { data: null } ]
|
|
414
|
-
} ) ).toThrow( ValidationError );
|
|
415
|
-
|
|
416
|
-
expect( () => parseGenerateImageArgs( {
|
|
417
|
-
prompt: 'image@v1',
|
|
418
|
-
images: [ { data: 'aW1hZ2U=', mediaType: '' } ]
|
|
419
|
-
} ) ).toThrow( ValidationError );
|
|
420
|
-
} );
|
|
421
|
-
|
|
422
|
-
it( 'rejects image strings that are not raw base64 data', () => {
|
|
423
|
-
for ( const image of [
|
|
424
|
-
'https://example.com/image.png',
|
|
425
|
-
'data:image/png;base64,aW1hZ2U=',
|
|
426
|
-
'not base64',
|
|
427
|
-
'abcde'
|
|
428
|
-
] ) {
|
|
429
|
-
expect( () => parseGenerateImageArgs( {
|
|
430
|
-
prompt: 'image@v1',
|
|
431
|
-
images: [ image ]
|
|
432
|
-
} ) ).toThrow( /Image strings must be raw base64 data/ );
|
|
433
|
-
}
|
|
434
|
-
} );
|
|
435
|
-
|
|
436
|
-
it( 'requires images when mask is provided', () => {
|
|
437
|
-
expect( () => parseGenerateImageArgs( {
|
|
438
|
-
prompt: 'image@v1',
|
|
439
|
-
mask: Buffer.from( 'mask-bytes' )
|
|
440
|
-
} ) ).toThrow( /mask requires images/ );
|
|
441
|
-
} );
|
|
442
|
-
|
|
443
|
-
it( 'throws ValidationError for unrecognized or prompt-owned call arguments', () => {
|
|
444
|
-
expect( () => parseGenerateImageArgs( {
|
|
445
|
-
prompt: 'image@v1',
|
|
446
|
-
size: '1024x1024',
|
|
447
|
-
n: 2
|
|
448
|
-
} ) ).toThrow( /unrecognized key/i );
|
|
449
|
-
|
|
450
|
-
expect( () => parseGenerateImageArgs( {
|
|
451
|
-
prompt: 'image@v1',
|
|
452
|
-
aspectRatio: '1:1'
|
|
453
|
-
} ) ).toThrow( /unrecognized key/i );
|
|
454
|
-
|
|
455
|
-
expect( () => parseGenerateImageArgs( {
|
|
456
|
-
prompt: 'image@v1',
|
|
457
|
-
providerOptions: { openai: { quality: 'high' } }
|
|
458
|
-
} ) ).toThrow( /unrecognized key/i );
|
|
459
|
-
|
|
460
|
-
expect( () => parseGenerateImageArgs( {
|
|
461
|
-
prompt: 'image@v1',
|
|
462
|
-
maxSteps: 4
|
|
463
|
-
} ) ).toThrow( /unrecognized key/i );
|
|
464
|
-
} );
|
|
465
|
-
} );
|
|
466
|
-
|
|
467
|
-
describe( 'parseAgentArgs', () => {
|
|
468
|
-
it( 'renames prompt to promptFile and keeps allowlisted leftovers', () => {
|
|
469
|
-
const tools = { search: searchTool };
|
|
470
|
-
const output = makeOutput();
|
|
471
|
-
const messageStore = { getMessages() {}, addMessages() {} };
|
|
472
|
-
|
|
473
|
-
const parsed = parseAgentArgs( {
|
|
474
|
-
prompt: 'summary@v1',
|
|
475
|
-
variables: { topic: 'testing' },
|
|
476
|
-
promptDir: '/prompts',
|
|
477
|
-
tools,
|
|
478
|
-
output,
|
|
479
|
-
stopWhen,
|
|
480
|
-
messageStore
|
|
481
|
-
} );
|
|
482
|
-
|
|
483
|
-
expect( parsed ).toEqual( {
|
|
484
|
-
promptFile: 'summary@v1',
|
|
485
|
-
variables: { topic: 'testing' },
|
|
486
|
-
promptDir: '/prompts',
|
|
487
|
-
tools,
|
|
488
|
-
output,
|
|
489
|
-
stopWhen,
|
|
490
|
-
messageStore
|
|
491
|
-
} );
|
|
492
|
-
expect( parsed.messageStore ).toBe( messageStore );
|
|
493
|
-
} );
|
|
494
|
-
|
|
495
|
-
it( 'preserves class-based message store identity', () => {
|
|
496
|
-
class TestMessageStore {
|
|
497
|
-
#messages = [];
|
|
498
|
-
|
|
499
|
-
getMessages() {
|
|
500
|
-
return this.#messages;
|
|
501
|
-
}
|
|
502
|
-
|
|
503
|
-
addMessages( messages ) {
|
|
504
|
-
this.#messages.push( ...messages );
|
|
505
|
-
}
|
|
506
|
-
}
|
|
507
|
-
|
|
508
|
-
const messageStore = new TestMessageStore();
|
|
509
|
-
const parsed = parseAgentArgs( {
|
|
510
|
-
prompt: 'summary@v1',
|
|
511
|
-
messageStore
|
|
512
|
-
} );
|
|
513
|
-
const message = { role: 'user', content: 'Hello' };
|
|
514
|
-
|
|
515
|
-
parsed.messageStore.addMessages( [ message ] );
|
|
516
|
-
|
|
517
|
-
expect( parsed.messageStore ).toBe( messageStore );
|
|
518
|
-
expect( parsed.messageStore.getMessages() ).toEqual( [ message ] );
|
|
519
|
-
} );
|
|
520
|
-
|
|
521
|
-
it( 'throws ValidationError with Agent prefix for invalid args', () => {
|
|
522
|
-
expect( () => parseAgentArgs( {} ) ).toThrow( ValidationError );
|
|
523
|
-
expect( () => parseAgentArgs( { prompt: '' } ) ).toThrow( /Invalid Agent\(\) arguments/ );
|
|
524
|
-
|
|
525
|
-
expect( () => parseAgentArgs( {
|
|
526
|
-
prompt: 'summary@v1',
|
|
527
|
-
promptDir: ''
|
|
528
|
-
} ) ).toThrow( ValidationError );
|
|
529
|
-
} );
|
|
530
|
-
|
|
531
|
-
it( 'does not keep prompt-owned call arguments on the parsed result', () => {
|
|
532
|
-
expect( () => parseAgentArgs( {
|
|
533
|
-
prompt: 'summary@v1',
|
|
534
|
-
skills: [ { name: 'style', description: 'Style', instructions: '# Style' } ]
|
|
535
|
-
} ) ).toThrow( /skills must be set in the prompt file/ );
|
|
536
|
-
|
|
537
|
-
expect( () => parseAgentArgs( {
|
|
538
|
-
prompt: 'summary@v1',
|
|
539
|
-
maxSteps: 4,
|
|
540
|
-
skills: [ './writer.md' ]
|
|
541
|
-
} ) ).toThrow( /must be set in the prompt file/ );
|
|
542
|
-
} );
|
|
543
|
-
|
|
544
|
-
it( 'throws ValidationError for unrecognized constructor arguments', () => {
|
|
545
|
-
expect( () => parseAgentArgs( {
|
|
546
|
-
prompt: 'summary@v1',
|
|
547
|
-
temperature: 0.2
|
|
548
|
-
} ) ).toThrow( /unrecognized key/i );
|
|
549
|
-
} );
|
|
550
|
-
|
|
551
|
-
it( 'throws ValidationError for an invalid message store', () => {
|
|
552
|
-
expect( () => parseAgentArgs( {
|
|
553
|
-
prompt: 'summary@v1',
|
|
554
|
-
messageStore: { getMessages() {} }
|
|
555
|
-
} ) ).toThrow( ValidationError );
|
|
556
|
-
} );
|
|
557
|
-
} );
|
|
558
|
-
|
|
559
|
-
describe( 'parseAgentGenerateArgs', () => {
|
|
560
|
-
it( 'defaults omitted args and messages to an empty list', () => {
|
|
561
|
-
expect( parseAgentGenerateArgs() ).toEqual( { messages: [] } );
|
|
562
|
-
expect( parseAgentGenerateArgs( undefined ) ).toEqual( { messages: [] } );
|
|
563
|
-
expect( parseAgentGenerateArgs( {} ) ).toEqual( { messages: [] } );
|
|
564
|
-
} );
|
|
565
|
-
|
|
566
|
-
it( 'keeps allowlisted generate options', () => {
|
|
567
|
-
const messages = [ { role: 'user', content: 'Hello' } ];
|
|
568
|
-
const abortSignal = AbortSignal.abort();
|
|
569
|
-
|
|
570
|
-
expect( parseAgentGenerateArgs( {
|
|
571
|
-
messages,
|
|
572
|
-
abortSignal,
|
|
573
|
-
toolChoice: 'required'
|
|
574
|
-
} ) ).toEqual( {
|
|
575
|
-
messages,
|
|
576
|
-
abortSignal,
|
|
577
|
-
toolChoice: 'required'
|
|
578
|
-
} );
|
|
579
|
-
} );
|
|
580
|
-
|
|
581
|
-
it( 'throws ValidationError for unrecognized or prompt-owned call arguments', () => {
|
|
582
|
-
expect( () => parseAgentGenerateArgs( { temperature: 0.2 } ) ).toThrow( /unrecognized key/i );
|
|
583
|
-
expect( () => parseAgentGenerateArgs( { onChunk: () => {} } ) ).toThrow( /unrecognized key/i );
|
|
584
|
-
expect( () => parseAgentGenerateArgs( { maxSteps: 4 } ) ).toThrow( /maxSteps must be set in the prompt file/ );
|
|
585
|
-
} );
|
|
586
|
-
|
|
587
|
-
it( 'throws ValidationError for invalid messages or toolChoice', () => {
|
|
588
|
-
expect( () => parseAgentGenerateArgs( {
|
|
589
|
-
messages: [ { content: 'Hello' } ]
|
|
590
|
-
} ) ).toThrow( ValidationError );
|
|
591
|
-
|
|
592
|
-
expect( () => parseAgentGenerateArgs( {
|
|
593
|
-
messages: [ { role: 'moderator', content: 'Hello' } ]
|
|
594
|
-
} ) ).toThrow( ValidationError );
|
|
595
|
-
|
|
596
|
-
expect( () => parseAgentGenerateArgs( {
|
|
597
|
-
toolChoice: { type: 'tool' }
|
|
598
|
-
} ) ).toThrow( ValidationError );
|
|
599
|
-
} );
|
|
600
|
-
} );
|
|
601
|
-
|
|
602
|
-
describe( 'parseAgentGenerateWithStreamingArgs', () => {
|
|
603
|
-
it( 'defaults omitted args and accepts onChunk', () => {
|
|
604
|
-
expect( parseAgentGenerateWithStreamingArgs() ).toEqual( { messages: [] } );
|
|
605
|
-
|
|
606
|
-
const parsed = parseAgentGenerateWithStreamingArgs( { onChunk: () => {} } );
|
|
607
|
-
expect( parsed.messages ).toEqual( [] );
|
|
608
|
-
expect( parsed.onChunk ).toBeTypeOf( 'function' );
|
|
609
|
-
} );
|
|
610
|
-
|
|
611
|
-
it( 'rejects stream-only callbacks', () => {
|
|
612
|
-
expect( () => parseAgentGenerateWithStreamingArgs( { onEnd: () => {} } ) ).toThrow( /unrecognized key/i );
|
|
613
|
-
} );
|
|
614
|
-
} );
|
|
615
|
-
|
|
616
|
-
describe( 'parseAgentStreamArgs', () => {
|
|
617
|
-
it( 'defaults omitted args and keeps stream callbacks', () => {
|
|
618
|
-
expect( parseAgentStreamArgs() ).toEqual( { messages: [] } );
|
|
619
|
-
|
|
620
|
-
const parsed = parseAgentStreamArgs( {
|
|
621
|
-
onEnd: () => {},
|
|
622
|
-
onError: () => {},
|
|
623
|
-
onChunk: () => {}
|
|
624
|
-
} );
|
|
625
|
-
|
|
626
|
-
expect( parsed.messages ).toEqual( [] );
|
|
627
|
-
expect( parsed.onEnd ).toBeTypeOf( 'function' );
|
|
628
|
-
expect( parsed.onError ).toBeTypeOf( 'function' );
|
|
629
|
-
expect( parsed.onChunk ).toBeTypeOf( 'function' );
|
|
630
|
-
} );
|
|
631
|
-
} );
|