@output.ai/llm 0.2.13 → 0.3.0-dev.pr341-daa6878

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.
@@ -1,4 +1,4 @@
1
- import { describe, it, expect } from 'vitest';
1
+ import { describe, it, expect, vi } from 'vitest';
2
2
  import { ValidationError } from '@output.ai/core';
3
3
  import { validatePrompt } from './prompt_validations.js';
4
4
 
@@ -65,6 +65,52 @@ describe( 'validatePrompt', () => {
65
65
  expect( () => validatePrompt( promptWithThinking ) ).not.toThrow();
66
66
  } );
67
67
 
68
+ it( 'should validate a prompt with thinking type disabled', () => {
69
+ const promptWithThinkingDisabled = {
70
+ name: 'thinking-disabled-prompt',
71
+ config: {
72
+ provider: 'anthropic',
73
+ model: 'claude-3-5-sonnet-20241022',
74
+ providerOptions: {
75
+ thinking: {
76
+ type: 'disabled'
77
+ }
78
+ }
79
+ },
80
+ messages: [
81
+ {
82
+ role: 'user',
83
+ content: 'Simple task.'
84
+ }
85
+ ]
86
+ };
87
+
88
+ expect( () => validatePrompt( promptWithThinkingDisabled ) ).not.toThrow();
89
+ } );
90
+
91
+ it( 'should validate a prompt with thinking without budgetTokens', () => {
92
+ const promptWithThinkingNoBudget = {
93
+ name: 'thinking-no-budget',
94
+ config: {
95
+ provider: 'anthropic',
96
+ model: 'claude-3-5-sonnet-20241022',
97
+ providerOptions: {
98
+ thinking: {
99
+ type: 'enabled'
100
+ }
101
+ }
102
+ },
103
+ messages: [
104
+ {
105
+ role: 'user',
106
+ content: 'Think about this.'
107
+ }
108
+ ]
109
+ };
110
+
111
+ expect( () => validatePrompt( promptWithThinkingNoBudget ) ).not.toThrow();
112
+ } );
113
+
68
114
  it( 'should validate a prompt with anthropic-specific providerOptions', () => {
69
115
  const promptWithAnthropicOptions = {
70
116
  name: 'anthropic-options-prompt',
@@ -174,11 +220,50 @@ describe( 'validatePrompt', () => {
174
220
  expect( () => validatePrompt( promptWithMixedOptions ) ).not.toThrow();
175
221
  } );
176
222
 
177
- it( 'should throw ValidationError when provider is invalid', () => {
178
- const invalidPrompt = {
179
- name: 'invalid-provider',
223
+ it( 'should accept custom provider names for dynamic providers', () => {
224
+ const customProviderPrompt = {
225
+ name: 'custom-provider-prompt',
226
+ config: {
227
+ provider: 'my-custom-provider',
228
+ model: 'custom-model-v1'
229
+ },
230
+ messages: [
231
+ {
232
+ role: 'user',
233
+ content: 'Test'
234
+ }
235
+ ]
236
+ };
237
+
238
+ expect( () => validatePrompt( customProviderPrompt ) ).not.toThrow();
239
+ } );
240
+
241
+ it( 'should accept extra config fields via passthrough', () => {
242
+ const extraFieldsPrompt = {
243
+ name: 'extra-fields-prompt',
244
+ config: {
245
+ provider: 'openai',
246
+ model: 'gpt-4',
247
+ topP: 0.9,
248
+ seed: 42,
249
+ stopSequences: [ 'END' ]
250
+ },
251
+ messages: [
252
+ {
253
+ role: 'user',
254
+ content: 'Test'
255
+ }
256
+ ]
257
+ };
258
+
259
+ expect( () => validatePrompt( extraFieldsPrompt ) ).not.toThrow();
260
+ } );
261
+
262
+ it( 'should throw ValidationError when provider is empty string', () => {
263
+ const emptyProviderPrompt = {
264
+ name: 'empty-provider',
180
265
  config: {
181
- provider: 'invalid-provider',
266
+ provider: '',
182
267
  model: 'some-model'
183
268
  },
184
269
  messages: [
@@ -189,7 +274,7 @@ describe( 'validatePrompt', () => {
189
274
  ]
190
275
  };
191
276
 
192
- expect( () => validatePrompt( invalidPrompt ) ).toThrow( ValidationError );
277
+ expect( () => validatePrompt( emptyProviderPrompt ) ).toThrow( ValidationError );
193
278
  } );
194
279
 
195
280
  it( 'should throw ValidationError when required fields are missing', () => {
@@ -209,7 +294,38 @@ describe( 'validatePrompt', () => {
209
294
  expect( () => validatePrompt( missingNamePrompt ) ).toThrow( ValidationError );
210
295
  } );
211
296
 
212
- it( 'should throw ValidationError when max tokens is snake_case', () => {
297
+ it( 'should pass through budget_tokens in thinking and warn about snake_case', () => {
298
+ const warnSpy = vi.spyOn( console, 'warn' ).mockImplementation( () => {} );
299
+
300
+ const promptWithBudgetTokensSnake = {
301
+ name: 'thinking-budget-snake',
302
+ config: {
303
+ provider: 'anthropic',
304
+ model: 'claude-sonnet-4-20250514',
305
+ providerOptions: {
306
+ thinking: {
307
+ type: 'enabled',
308
+ budget_tokens: 10000
309
+ }
310
+ }
311
+ },
312
+ messages: [
313
+ {
314
+ role: 'user',
315
+ content: 'Think hard.'
316
+ }
317
+ ]
318
+ };
319
+
320
+ expect( () => validatePrompt( promptWithBudgetTokensSnake ) ).not.toThrow();
321
+ expect( warnSpy ).toHaveBeenCalledWith(
322
+ '[output-llm] "budget_tokens" found in providerOptions.thinking. Did you mean "budgetTokens"?'
323
+ );
324
+
325
+ warnSpy.mockRestore();
326
+ } );
327
+
328
+ it( 'should allow snake_case fields in config via passthrough (no longer strict)', () => {
213
329
  const maxTokensSnakeCase = {
214
330
  name: 'test-prompt',
215
331
  config: {
@@ -225,6 +341,6 @@ describe( 'validatePrompt', () => {
225
341
  ]
226
342
  };
227
343
 
228
- expect( () => validatePrompt( maxTokensSnakeCase ) ).toThrow( ValidationError );
344
+ expect( () => validatePrompt( maxTokensSnakeCase ) ).not.toThrow();
229
345
  } );
230
346
  } );
@@ -5,17 +5,7 @@ const generateTextArgsSchema = z.object( {
5
5
  variables: z.any().optional()
6
6
  } );
7
7
 
8
- const generateObjectArgsSchema = z.object( {
9
- prompt: z.string(),
10
- variables: z.any().optional(),
11
- schema: z.custom( v => v instanceof z.ZodObject, {
12
- message: 'schema must be a ZodObject'
13
- } ),
14
- schemaName: z.string().optional(),
15
- schemaDescription: z.string().optional()
16
- } );
17
-
18
- const generateArrayArgsSchema = z.object( {
8
+ const generateSchemaArgsSchema = z.object( {
19
9
  prompt: z.string(),
20
10
  variables: z.any().optional(),
21
11
  schema: z.custom( v => v instanceof z.ZodType, {
@@ -43,11 +33,11 @@ export function validateGenerateTextArgs( args ) {
43
33
  }
44
34
 
45
35
  export function validateGenerateObjectArgs( args ) {
46
- validateSchema( generateObjectArgsSchema, args, 'Invalid generateObject() arguments' );
36
+ validateSchema( generateSchemaArgsSchema, args, 'Invalid generateObject() arguments' );
47
37
  }
48
38
 
49
39
  export function validateGenerateArrayArgs( args ) {
50
- validateSchema( generateArrayArgsSchema, args, 'Invalid generateArray() arguments' );
40
+ validateSchema( generateSchemaArgsSchema, args, 'Invalid generateArray() arguments' );
51
41
  }
52
42
 
53
43
  export function validateGenerateEnumArgs( args ) {