@outputai/llm 0.6.1-dev.aab2335.0 → 0.6.1-next.0d08ff5.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.
Files changed (45) hide show
  1. package/package.json +2 -2
  2. package/src/agent.js +15 -9
  3. package/src/agent.spec.js +295 -214
  4. package/src/ai_model.js +79 -36
  5. package/src/ai_model.spec.js +31 -13
  6. package/src/ai_sdk.js +55 -79
  7. package/src/ai_sdk.spec.js +464 -611
  8. package/src/ai_sdk_options.js +61 -0
  9. package/src/ai_sdk_options.spec.js +164 -0
  10. package/src/cost/index.js +1 -1
  11. package/src/index.d.ts +230 -175
  12. package/src/index.js +2 -2
  13. package/src/prompt/escape.js +65 -0
  14. package/src/prompt/escape.spec.js +159 -0
  15. package/src/{load_content.js → prompt/load_content.js} +1 -22
  16. package/src/{load_content.spec.js → prompt/load_content.spec.js} +6 -6
  17. package/src/prompt/loader.js +49 -0
  18. package/src/prompt/loader.spec.js +274 -0
  19. package/src/{prompt_loader_validation.spec.js → prompt/loader_validation.spec.js} +40 -7
  20. package/src/prompt/parser.js +19 -0
  21. package/src/{parser.spec.js → prompt/parser.spec.js} +74 -29
  22. package/src/prompt/prepare_text.js +27 -0
  23. package/src/prompt/prepare_text.spec.js +141 -0
  24. package/src/{skill.js → prompt/skill.js} +19 -0
  25. package/src/prompt/skill.spec.js +172 -0
  26. package/src/{prompt_validations.js → prompt/validations.js} +32 -6
  27. package/src/{prompt_validations.spec.js → prompt/validations.spec.js} +189 -1
  28. package/src/utils/__fixtures__/image_response.json +38 -0
  29. package/src/utils/__fixtures__/stream_response.json +294 -0
  30. package/src/utils/__fixtures__/text_response.json +201 -0
  31. package/src/utils/error_handler.js +65 -0
  32. package/src/utils/error_handler.spec.js +195 -0
  33. package/src/utils/image.js +10 -0
  34. package/src/utils/image.spec.js +20 -0
  35. package/src/utils/response_wrappers.js +46 -19
  36. package/src/utils/response_wrappers.spec.js +130 -70
  37. package/src/utils/source_extraction.js +17 -27
  38. package/src/utils/trace.js +2 -3
  39. package/src/utils/trace.spec.js +9 -13
  40. package/src/validations.js +54 -2
  41. package/src/validations.spec.js +166 -0
  42. package/src/parser.js +0 -28
  43. package/src/prompt_loader.js +0 -80
  44. package/src/prompt_loader.spec.js +0 -358
  45. package/src/skill.d.ts +0 -49
@@ -1,6 +1,6 @@
1
1
  import { describe, it, expect, vi } from 'vitest';
2
2
  import { ValidationError } from '@outputai/core';
3
- import { validatePrompt } from './prompt_validations.js';
3
+ import { validatePrompt } from './validations.js';
4
4
 
5
5
  describe( 'validatePrompt', () => {
6
6
  it( 'should validate a correct prompt with all required fields', () => {
@@ -238,6 +238,194 @@ describe( 'validatePrompt', () => {
238
238
  expect( () => validatePrompt( customProviderPrompt ) ).not.toThrow();
239
239
  } );
240
240
 
241
+ it( 'should validate image generation config fields', () => {
242
+ const imagePrompt = {
243
+ name: 'image-prompt',
244
+ config: {
245
+ provider: 'openai',
246
+ model: 'gpt-image-1',
247
+ n: 2,
248
+ maxImagesPerCall: 1,
249
+ size: '1024x1024',
250
+ aspectRatio: '1:1',
251
+ seed: 42,
252
+ providerOptions: {
253
+ openai: {
254
+ quality: 'high'
255
+ }
256
+ }
257
+ },
258
+ messages: [
259
+ {
260
+ role: 'user',
261
+ content: 'Generate an image of a mountain.'
262
+ }
263
+ ]
264
+ };
265
+
266
+ expect( () => validatePrompt( imagePrompt ) ).not.toThrow();
267
+ } );
268
+
269
+ it( 'should validate a plain-instructions prompt without messages', () => {
270
+ const instructionsPrompt = {
271
+ name: 'image-prompt',
272
+ config: {
273
+ provider: 'openai',
274
+ model: 'gpt-image-1'
275
+ },
276
+ messages: [],
277
+ instructions: 'Generate an image of a mountain.'
278
+ };
279
+
280
+ expect( () => validatePrompt( instructionsPrompt ) ).not.toThrow();
281
+ } );
282
+
283
+ it( 'should validate role messages with null instructions', () => {
284
+ const messagesPrompt = {
285
+ name: 'messages-prompt',
286
+ config: {
287
+ provider: 'anthropic',
288
+ model: 'claude-3-opus-20240229'
289
+ },
290
+ messages: [
291
+ {
292
+ role: 'user',
293
+ content: 'Write a summary.'
294
+ }
295
+ ],
296
+ instructions: null
297
+ };
298
+
299
+ expect( () => validatePrompt( messagesPrompt ) ).not.toThrow();
300
+ } );
301
+
302
+ it( 'should throw ValidationError when instructions are only whitespace', () => {
303
+ const whitespaceInstructionsPrompt = {
304
+ name: 'empty-instructions-prompt',
305
+ config: {
306
+ provider: 'openai',
307
+ model: 'gpt-image-1'
308
+ },
309
+ messages: [],
310
+ instructions: ' '
311
+ };
312
+
313
+ expect( () => validatePrompt( whitespaceInstructionsPrompt ) ).toThrow( ValidationError );
314
+ } );
315
+
316
+ it( 'should throw ValidationError when both messages and instructions are present', () => {
317
+ const mixedPrompt = {
318
+ name: 'mixed-prompt',
319
+ config: {
320
+ provider: 'anthropic',
321
+ model: 'claude-3-opus-20240229'
322
+ },
323
+ messages: [
324
+ {
325
+ role: 'user',
326
+ content: 'Write a summary.'
327
+ }
328
+ ],
329
+ instructions: 'Plain instructions should not be mixed with messages.'
330
+ };
331
+
332
+ expect( () => validatePrompt( mixedPrompt ) ).toThrow( ValidationError );
333
+ } );
334
+
335
+ it( 'should throw ValidationError when neither messages nor instructions are present', () => {
336
+ const emptyPrompt = {
337
+ name: 'empty-prompt',
338
+ config: {
339
+ provider: 'anthropic',
340
+ model: 'claude-3-opus-20240229'
341
+ },
342
+ messages: [],
343
+ instructions: null
344
+ };
345
+
346
+ expect( () => validatePrompt( emptyPrompt ) ).toThrow( ValidationError );
347
+ } );
348
+
349
+ it( 'should validate a prompt with skill path config', () => {
350
+ const promptWithSkills = {
351
+ name: 'skills-prompt',
352
+ config: {
353
+ provider: 'anthropic',
354
+ model: 'claude-3-opus-20240229',
355
+ skills: [ './skills', './review.md' ]
356
+ },
357
+ messages: [
358
+ {
359
+ role: 'user',
360
+ content: 'Review this.'
361
+ }
362
+ ]
363
+ };
364
+
365
+ expect( () => validatePrompt( promptWithSkills ) ).not.toThrow();
366
+ } );
367
+
368
+ it( 'should validate a prompt with a single skill path config', () => {
369
+ const promptWithSkill = {
370
+ name: 'skill-prompt',
371
+ config: {
372
+ provider: 'anthropic',
373
+ model: 'claude-3-opus-20240229',
374
+ skills: './skills'
375
+ },
376
+ messages: [
377
+ {
378
+ role: 'user',
379
+ content: 'Review this.'
380
+ }
381
+ ]
382
+ };
383
+
384
+ expect( () => validatePrompt( promptWithSkill ) ).not.toThrow();
385
+ } );
386
+
387
+ it( 'should throw ValidationError for invalid skill path config', () => {
388
+ const invalidSkillsPrompt = {
389
+ name: 'invalid-skills-prompt',
390
+ config: {
391
+ provider: 'anthropic',
392
+ model: 'claude-3-opus-20240229',
393
+ skills: [ './skills', '' ]
394
+ },
395
+ messages: [
396
+ {
397
+ role: 'user',
398
+ content: 'Review this.'
399
+ }
400
+ ]
401
+ };
402
+
403
+ expect( () => validatePrompt( invalidSkillsPrompt ) ).toThrow( ValidationError );
404
+ } );
405
+
406
+ it( 'should throw ValidationError for invalid image generation config fields', () => {
407
+ const invalidImagePrompt = {
408
+ name: 'invalid-image-prompt',
409
+ config: {
410
+ provider: 'openai',
411
+ model: 'gpt-image-1',
412
+ n: 0,
413
+ maxImagesPerCall: 1.5,
414
+ size: 'square',
415
+ aspectRatio: '16x9',
416
+ seed: 1.2
417
+ },
418
+ messages: [
419
+ {
420
+ role: 'user',
421
+ content: 'Generate an image of a mountain.'
422
+ }
423
+ ]
424
+ };
425
+
426
+ expect( () => validatePrompt( invalidImagePrompt ) ).toThrow( ValidationError );
427
+ } );
428
+
241
429
  it( 'should accept extra config fields via passthrough', () => {
242
430
  const extraFieldsPrompt = {
243
431
  name: 'extra-fields-prompt',
@@ -0,0 +1,38 @@
1
+ {
2
+ "images": [
3
+ {
4
+ "base64Data": "<<removed as it was a very large base64 representation of the image>>",
5
+ "mediaType": "image/png"
6
+ }
7
+ ],
8
+ "warnings": [],
9
+ "responses": [
10
+ {
11
+ "timestamp": "2026-06-04T15:29:53.450Z",
12
+ "modelId": "gpt-image-1",
13
+ "headers": {
14
+ "__redacted__": "<<headers were removed>>"
15
+ }
16
+ }
17
+ ],
18
+ "providerMetadata": {
19
+ "openai": {
20
+ "images": [
21
+ {
22
+ "created": 1780587031,
23
+ "size": "1024x1024",
24
+ "quality": "high",
25
+ "background": "opaque",
26
+ "outputFormat": "png",
27
+ "imageTokens": 0,
28
+ "textTokens": 138
29
+ }
30
+ ]
31
+ }
32
+ },
33
+ "usage": {
34
+ "inputTokens": 138,
35
+ "outputTokens": 4160,
36
+ "totalTokens": 4298
37
+ }
38
+ }
@@ -0,0 +1,294 @@
1
+ {
2
+ "stepNumber": 0,
3
+ "model": {
4
+ "provider": "anthropic.messages",
5
+ "modelId": "claude-sonnet-4-20250514"
6
+ },
7
+ "finishReason": "stop",
8
+ "rawFinishReason": "end_turn",
9
+ "totalUsage": {
10
+ "inputTokens": 49,
11
+ "inputTokenDetails": {
12
+ "noCacheTokens": 49,
13
+ "cacheReadTokens": 0,
14
+ "cacheWriteTokens": 0
15
+ },
16
+ "outputTokens": 160,
17
+ "outputTokenDetails": {},
18
+ "totalTokens": 209,
19
+ "cachedInputTokens": 0
20
+ },
21
+ "usage": {
22
+ "inputTokens": 49,
23
+ "inputTokenDetails": {
24
+ "noCacheTokens": 49,
25
+ "cacheReadTokens": 0,
26
+ "cacheWriteTokens": 0
27
+ },
28
+ "outputTokens": 160,
29
+ "outputTokenDetails": {},
30
+ "totalTokens": 209,
31
+ "raw": {
32
+ "input_tokens": 49,
33
+ "cache_creation_input_tokens": 0,
34
+ "cache_read_input_tokens": 0,
35
+ "cache_creation": {
36
+ "ephemeral_5m_input_tokens": 0,
37
+ "ephemeral_1h_input_tokens": 0
38
+ },
39
+ "output_tokens": 160,
40
+ "service_tier": "standard",
41
+ "inference_geo": "not_available"
42
+ },
43
+ "cachedInputTokens": 0
44
+ },
45
+ "content": [
46
+ {
47
+ "type": "text",
48
+ "text": "A carburetor is a mechanical device that was once the heart of nearly every gasoline engine, responsible for mixing air and fuel in the precise proportions needed for combustion. This ingenious invention uses the principles of airflow and vacuum pressure to draw fuel from a reservoir and atomize it with incoming air, creating a combustible mixture that powers the engine. While carburetors dominated automotive technology for decades\u2014from the early 1900s through the 1980s\u2014they have largely been replaced by fuel injection systems in modern vehicles due to their superior efficiency and emissions control. However, carburetors remain popular in motorcycles, small engines, and classic cars, where enthusiasts appreciate their mechanical simplicity and the hands-on tuning experience they provide."
49
+ }
50
+ ],
51
+ "text": "A carburetor is a mechanical device that was once the heart of nearly every gasoline engine, responsible for mixing air and fuel in the precise proportions needed for combustion. This ingenious invention uses the principles of airflow and vacuum pressure to draw fuel from a reservoir and atomize it with incoming air, creating a combustible mixture that powers the engine. While carburetors dominated automotive technology for decades\u2014from the early 1900s through the 1980s\u2014they have largely been replaced by fuel injection systems in modern vehicles due to their superior efficiency and emissions control. However, carburetors remain popular in motorcycles, small engines, and classic cars, where enthusiasts appreciate their mechanical simplicity and the hands-on tuning experience they provide.",
52
+ "reasoning": [],
53
+ "files": [],
54
+ "sources": [],
55
+ "toolCalls": [
56
+ {
57
+ "type": "tool-call",
58
+ "toolCallId": "call_fixture_web_search",
59
+ "toolName": "webSearch",
60
+ "input": {
61
+ "query": "carburetor reference article"
62
+ }
63
+ }
64
+ ],
65
+ "staticToolCalls": [],
66
+ "dynamicToolCalls": [],
67
+ "toolResults": [
68
+ {
69
+ "type": "tool-result",
70
+ "toolCallId": "call_fixture_web_search",
71
+ "toolName": "webSearch",
72
+ "input": {
73
+ "query": "carburetor reference article"
74
+ },
75
+ "output": {
76
+ "results": [
77
+ {
78
+ "title": "Carburetor reference article",
79
+ "url": "https://example.com/carburetor-reference"
80
+ }
81
+ ]
82
+ }
83
+ }
84
+ ],
85
+ "staticToolResults": [],
86
+ "dynamicToolResults": [],
87
+ "request": {
88
+ "body": {
89
+ "model": "claude-sonnet-4-20250514",
90
+ "max_tokens": 1024,
91
+ "temperature": 0.7,
92
+ "messages": [
93
+ {
94
+ "role": "assistant",
95
+ "content": [
96
+ {
97
+ "type": "text",
98
+ "text": "You are an expert content writer who produces clear, engaging, and informative text."
99
+ }
100
+ ]
101
+ },
102
+ {
103
+ "role": "user",
104
+ "content": [
105
+ {
106
+ "type": "text",
107
+ "text": "Write a short paragraph about carburetors."
108
+ }
109
+ ]
110
+ }
111
+ ],
112
+ "stream": true
113
+ }
114
+ },
115
+ "response": {
116
+ "id": "msg_012FXnYFEVPZ6GPoneL32k9J",
117
+ "timestamp": "2026-06-04T15:30:08.347Z",
118
+ "modelId": "claude-sonnet-4-20250514",
119
+ "headers": {
120
+ "__redacted__": "<<headers were removed>>"
121
+ },
122
+ "messages": [
123
+ {
124
+ "role": "assistant",
125
+ "content": [
126
+ {
127
+ "type": "text",
128
+ "text": "A carburetor is a mechanical device that was once the heart of nearly every gasoline engine, responsible for mixing air and fuel in the precise proportions needed for combustion. This ingenious invention uses the principles of airflow and vacuum pressure to draw fuel from a reservoir and atomize it with incoming air, creating a combustible mixture that powers the engine. While carburetors dominated automotive technology for decades\u2014from the early 1900s through the 1980s\u2014they have largely been replaced by fuel injection systems in modern vehicles due to their superior efficiency and emissions control. However, carburetors remain popular in motorcycles, small engines, and classic cars, where enthusiasts appreciate their mechanical simplicity and the hands-on tuning experience they provide."
129
+ }
130
+ ]
131
+ }
132
+ ]
133
+ },
134
+ "warnings": [],
135
+ "providerMetadata": {
136
+ "anthropic": {
137
+ "usage": {
138
+ "input_tokens": 49,
139
+ "cache_creation_input_tokens": 0,
140
+ "cache_read_input_tokens": 0,
141
+ "cache_creation": {
142
+ "ephemeral_5m_input_tokens": 0,
143
+ "ephemeral_1h_input_tokens": 0
144
+ },
145
+ "output_tokens": 160,
146
+ "service_tier": "standard",
147
+ "inference_geo": "not_available"
148
+ },
149
+ "cacheCreationInputTokens": 0,
150
+ "stopSequence": null,
151
+ "iterations": null,
152
+ "container": null,
153
+ "contextManagement": null
154
+ }
155
+ },
156
+ "steps": [
157
+ {
158
+ "stepNumber": 0,
159
+ "model": {
160
+ "provider": "anthropic.messages",
161
+ "modelId": "claude-sonnet-4-20250514"
162
+ },
163
+ "content": [
164
+ {
165
+ "type": "text",
166
+ "text": "A carburetor is a mechanical device that was once the heart of nearly every gasoline engine, responsible for mixing air and fuel in the precise proportions needed for combustion. This ingenious invention uses the principles of airflow and vacuum pressure to draw fuel from a reservoir and atomize it with incoming air, creating a combustible mixture that powers the engine. While carburetors dominated automotive technology for decades\u2014from the early 1900s through the 1980s\u2014they have largely been replaced by fuel injection systems in modern vehicles due to their superior efficiency and emissions control. However, carburetors remain popular in motorcycles, small engines, and classic cars, where enthusiasts appreciate their mechanical simplicity and the hands-on tuning experience they provide."
167
+ }
168
+ ],
169
+ "finishReason": "stop",
170
+ "rawFinishReason": "end_turn",
171
+ "usage": {
172
+ "inputTokens": 49,
173
+ "inputTokenDetails": {
174
+ "noCacheTokens": 49,
175
+ "cacheReadTokens": 0,
176
+ "cacheWriteTokens": 0
177
+ },
178
+ "outputTokens": 160,
179
+ "outputTokenDetails": {},
180
+ "totalTokens": 209,
181
+ "raw": {
182
+ "input_tokens": 49,
183
+ "cache_creation_input_tokens": 0,
184
+ "cache_read_input_tokens": 0,
185
+ "cache_creation": {
186
+ "ephemeral_5m_input_tokens": 0,
187
+ "ephemeral_1h_input_tokens": 0
188
+ },
189
+ "output_tokens": 160,
190
+ "service_tier": "standard",
191
+ "inference_geo": "not_available"
192
+ },
193
+ "cachedInputTokens": 0
194
+ },
195
+ "warnings": [],
196
+ "request": {
197
+ "body": {
198
+ "model": "claude-sonnet-4-20250514",
199
+ "max_tokens": 1024,
200
+ "temperature": 0.7,
201
+ "messages": [
202
+ {
203
+ "role": "assistant",
204
+ "content": [
205
+ {
206
+ "type": "text",
207
+ "text": "You are an expert content writer who produces clear, engaging, and informative text."
208
+ }
209
+ ]
210
+ },
211
+ {
212
+ "role": "user",
213
+ "content": [
214
+ {
215
+ "type": "text",
216
+ "text": "Write a short paragraph about carburetors."
217
+ }
218
+ ]
219
+ }
220
+ ],
221
+ "stream": true
222
+ }
223
+ },
224
+ "response": {
225
+ "id": "msg_012FXnYFEVPZ6GPoneL32k9J",
226
+ "timestamp": "2026-06-04T15:30:08.347Z",
227
+ "modelId": "claude-sonnet-4-20250514",
228
+ "headers": {
229
+ "__redacted__": "<<headers were removed>>"
230
+ },
231
+ "messages": [
232
+ {
233
+ "role": "assistant",
234
+ "content": [
235
+ {
236
+ "type": "text",
237
+ "text": "A carburetor is a mechanical device that was once the heart of nearly every gasoline engine, responsible for mixing air and fuel in the precise proportions needed for combustion. This ingenious invention uses the principles of airflow and vacuum pressure to draw fuel from a reservoir and atomize it with incoming air, creating a combustible mixture that powers the engine. While carburetors dominated automotive technology for decades\u2014from the early 1900s through the 1980s\u2014they have largely been replaced by fuel injection systems in modern vehicles due to their superior efficiency and emissions control. However, carburetors remain popular in motorcycles, small engines, and classic cars, where enthusiasts appreciate their mechanical simplicity and the hands-on tuning experience they provide."
238
+ }
239
+ ]
240
+ }
241
+ ]
242
+ },
243
+ "providerMetadata": {
244
+ "anthropic": {
245
+ "usage": {
246
+ "input_tokens": 49,
247
+ "cache_creation_input_tokens": 0,
248
+ "cache_read_input_tokens": 0,
249
+ "cache_creation": {
250
+ "ephemeral_5m_input_tokens": 0,
251
+ "ephemeral_1h_input_tokens": 0
252
+ },
253
+ "output_tokens": 160,
254
+ "service_tier": "standard",
255
+ "inference_geo": "not_available"
256
+ },
257
+ "cacheCreationInputTokens": 0,
258
+ "stopSequence": null,
259
+ "iterations": null,
260
+ "container": null,
261
+ "contextManagement": null
262
+ }
263
+ },
264
+ "toolCalls": [
265
+ {
266
+ "type": "tool-call",
267
+ "toolCallId": "call_fixture_web_search",
268
+ "toolName": "webSearch",
269
+ "input": {
270
+ "query": "carburetor reference article"
271
+ }
272
+ }
273
+ ],
274
+ "toolResults": [
275
+ {
276
+ "type": "tool-result",
277
+ "toolCallId": "call_fixture_web_search",
278
+ "toolName": "webSearch",
279
+ "input": {
280
+ "query": "carburetor reference article"
281
+ },
282
+ "output": {
283
+ "results": [
284
+ {
285
+ "title": "Carburetor reference article",
286
+ "url": "https://example.com/carburetor-reference"
287
+ }
288
+ ]
289
+ }
290
+ }
291
+ ]
292
+ }
293
+ ]
294
+ }