@outputai/llm 0.11.1-next.a5ded18.0 → 0.11.1-next.b4488bf.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 (43) hide show
  1. package/package.json +17 -16
  2. package/src/agent.js +16 -17
  3. package/src/agent.spec.js +27 -27
  4. package/src/ai_sdk_options.js +9 -9
  5. package/src/ai_sdk_options.spec.js +10 -10
  6. package/src/fixtures/image_response_v7_google_vertex.js +72 -0
  7. package/src/fixtures/image_response_v7_openai.js +77 -0
  8. package/src/fixtures/models_api_v7_subset.json +259 -0
  9. package/src/fixtures/stream_response_v7_anthropic.js +389 -0
  10. package/src/fixtures/stream_response_v7_google_vertex.js +462 -0
  11. package/src/fixtures/stream_response_v7_openai.js +353 -0
  12. package/src/fixtures/stream_response_v7_perplexity.js +671 -0
  13. package/src/fixtures/text_response_v7_anthropic.js +359 -0
  14. package/src/fixtures/text_response_v7_anthropic_cache.js +719 -0
  15. package/src/fixtures/text_response_v7_google_vertex.js +435 -0
  16. package/src/fixtures/text_response_v7_openai.js +347 -0
  17. package/src/fixtures/text_response_v7_perplexity.js +764 -0
  18. package/src/generate.js +4 -4
  19. package/src/generate.spec.js +15 -15
  20. package/src/index.d.ts +113 -59
  21. package/src/utils/cost.full.spec.js +272 -0
  22. package/src/utils/cost.js +137 -0
  23. package/src/utils/cost.spec.js +290 -0
  24. package/src/utils/legacy_cost_attribute.js +75 -0
  25. package/src/utils/legacy_cost_attribute.spec.js +278 -0
  26. package/src/utils/models_pricing.js +68 -0
  27. package/src/{cost/fetch_models_pricing.spec.js → utils/models_pricing.spec.js} +3 -9
  28. package/src/utils/stream.js +2 -2
  29. package/src/utils/stream.spec.js +2 -2
  30. package/src/utils/usage.full.spec.js +215 -0
  31. package/src/utils/usage.js +121 -0
  32. package/src/utils/usage.spec.js +246 -0
  33. package/src/utils/wrap.js +42 -25
  34. package/src/utils/wrap.spec.js +162 -54
  35. package/src/validations.js +2 -2
  36. package/src/validations.spec.js +7 -7
  37. package/src/cost/fetch_models_pricing.js +0 -60
  38. package/src/cost/index.js +0 -58
  39. package/src/cost/index.spec.js +0 -350
  40. package/src/utils/__fixtures__/image_response.json +0 -38
  41. package/src/utils/__fixtures__/stream_response.json +0 -294
  42. package/src/utils/__fixtures__/text_response.json +0 -201
  43. /package/src/{cost/fixtures → fixtures}/models_api_light.json +0 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@outputai/llm",
3
- "version": "0.11.1-next.a5ded18.0",
3
+ "version": "0.11.1-next.b4488bf.0",
4
4
  "description": "Framework abstraction to interact with LLM models",
5
5
  "type": "module",
6
6
  "main": "src/index.js",
@@ -12,29 +12,30 @@
12
12
  "./src"
13
13
  ],
14
14
  "dependencies": {
15
+ "decimal.js": "10.6.0",
15
16
  "entities": "8.0.0",
16
17
  "gray-matter": "4.0.3",
17
18
  "liquidjs": "10.27.2",
18
19
  "undici": "8.9.0",
19
- "@outputai/core": "0.11.1-next.a5ded18.0"
20
+ "@outputai/core": "0.11.1-next.b4488bf.0"
20
21
  },
21
22
  "devDependencies": {
22
- "@ai-sdk/amazon-bedrock": "4.0.138",
23
- "@ai-sdk/anthropic": "3.0.81",
24
- "@ai-sdk/azure": "3.0.68",
25
- "@ai-sdk/google-vertex": "4.0.140",
26
- "@ai-sdk/openai": "3.0.67",
27
- "@ai-sdk/perplexity": "3.0.33",
28
- "ai": "6.0.168"
23
+ "@ai-sdk/amazon-bedrock": "5.0.57",
24
+ "@ai-sdk/anthropic": "4.0.39",
25
+ "@ai-sdk/azure": "4.0.43",
26
+ "@ai-sdk/google-vertex": "5.0.54",
27
+ "@ai-sdk/openai": "4.0.42",
28
+ "@ai-sdk/perplexity": "4.0.29",
29
+ "ai": "7.0.66"
29
30
  },
30
31
  "peerDependencies": {
31
- "@ai-sdk/amazon-bedrock": ">=4 <5",
32
- "@ai-sdk/anthropic": ">=3 <4",
33
- "@ai-sdk/azure": ">=3 <4",
34
- "@ai-sdk/google-vertex": ">=4 <5",
35
- "@ai-sdk/openai": ">=3 <4",
36
- "@ai-sdk/perplexity": ">=3 <4",
37
- "ai": ">=6 <7"
32
+ "@ai-sdk/amazon-bedrock": ">=5 <6",
33
+ "@ai-sdk/anthropic": ">=4 <5",
34
+ "@ai-sdk/azure": ">=4 <5",
35
+ "@ai-sdk/google-vertex": ">=5 <6",
36
+ "@ai-sdk/openai": ">=4 <5",
37
+ "@ai-sdk/perplexity": ">=4 <5",
38
+ "ai": ">=7 <8"
38
39
  },
39
40
  "license": "Apache-2.0",
40
41
  "publishConfig": {
package/src/agent.js CHANGED
@@ -8,7 +8,8 @@ import { loadSkills } from './utils/skills.js';
8
8
  import * as Validator from './validations.js';
9
9
  import { Logger } from '@outputai/core';
10
10
 
11
- export class Agent extends AIToolLoopAgent {
11
+ export class Agent {
12
+ #agent;
12
13
  #prompt;
13
14
  #initialMessages;
14
15
  #store;
@@ -19,13 +20,11 @@ export class Agent extends AIToolLoopAgent {
19
20
  const prompt = promptObject ?? loadPrompt( promptFile, variables, promptDir );
20
21
  const skills = loadSkills( prompt );
21
22
 
22
- const { system, messages, ...aiOptions } = loadAiSdkTextOptions( { prompt, skills, tools, output, stopWhen } );
23
+ const { instructions, messages, ...aiOptions } = loadAiSdkTextOptions( { prompt, skills, tools, output, stopWhen } );
23
24
 
24
- // loadAiSdkTextOptions routes system blocks to the `system` slot (preserving
25
- // per-message providerOptions); pass them as the agent's `instructions`.
26
- super( {
25
+ this.#agent = new AIToolLoopAgent( {
27
26
  ...aiOptions,
28
- ...( system.length > 0 ? { instructions: system } : {} )
27
+ ...( instructions.length > 0 ? { instructions } : {} )
29
28
  } );
30
29
 
31
30
  this.#prompt = prompt;
@@ -53,13 +52,13 @@ export class Agent extends AIToolLoopAgent {
53
52
  name: 'Agent.generate',
54
53
  prompt: this.#prompt,
55
54
  fn: async () => {
56
- const response = await super.generate( {
55
+ const response = await this.#agent.generate( {
57
56
  messages: combinedMessages,
58
57
  allowSystemInMessages: true,
59
58
  ...( abortSignal && { abortSignal } ),
60
59
  ...( toolChoice && { toolChoice } )
61
60
  } );
62
- await this.#storeMessages( messages.concat( response.response?.messages ?? [] ) );
61
+ await this.#storeMessages( messages.concat( response.responseMessages ?? [] ) );
63
62
  return response;
64
63
  }
65
64
  } );
@@ -77,13 +76,13 @@ export class Agent extends AIToolLoopAgent {
77
76
  prompt: this.#prompt,
78
77
  fn: async () => {
79
78
  const state = { response: null };
80
- const stream = await super.stream( {
79
+ const stream = await this.#agent.stream( {
81
80
  messages: combinedMessages,
82
81
  allowSystemInMessages: true,
83
82
  ...( onChunk && { onChunk } ),
84
83
  ...( abortSignal && { abortSignal } ),
85
84
  ...( toolChoice && { toolChoice } ),
86
- onFinish: res => {
85
+ onEnd: res => {
87
86
  state.response = res;
88
87
  },
89
88
  onError: _ => {} // Suppress AI-SDK console printing
@@ -96,7 +95,7 @@ export class Agent extends AIToolLoopAgent {
96
95
  }
97
96
 
98
97
  state.response.output = await stream.output;
99
- await this.#storeMessages( messages.concat( state.response.response?.messages ?? [] ) );
98
+ await this.#storeMessages( messages.concat( state.response.responseMessages ?? [] ) );
100
99
 
101
100
  return state.response;
102
101
  }
@@ -104,28 +103,28 @@ export class Agent extends AIToolLoopAgent {
104
103
  }
105
104
 
106
105
  async stream( args ) {
107
- const { messages, abortSignal, toolChoice, onChunk, onFinish, onError } = Validator.parseAgentStreamArgs( args );
106
+ const { messages, abortSignal, toolChoice, onChunk, onEnd, onError } = Validator.parseAgentStreamArgs( args );
108
107
  const combinedMessages = await this.#combineWithPreviousMessages( messages );
109
108
 
110
109
  return wrapStream( {
111
110
  name: 'Agent.stream',
112
111
  prompt: this.#prompt,
113
- fn: ( { onFinishHook, onErrorHook } ) => super.stream( {
112
+ fn: ( { onEndHook, onErrorHook } ) => this.#agent.stream( {
114
113
  messages: combinedMessages,
115
114
  allowSystemInMessages: true,
116
115
  ...( onChunk && { onChunk } ),
117
116
  ...( abortSignal && { abortSignal } ),
118
117
  ...( toolChoice && { toolChoice } ),
119
- onFinish: response =>
120
- onFinishHook( response, async parsedResponse => {
118
+ onEnd: response =>
119
+ onEndHook( response, async parsedResponse => {
121
120
  if ( response.finishReason !== 'error' ) {
122
- await this.#storeMessages( messages.concat( response.response?.messages ?? [] ) )
121
+ await this.#storeMessages( messages.concat( response.responseMessages ?? [] ) )
123
122
  .catch( error => Logger.error( 'Agent.stream message store persistence failed', {
124
123
  namespace: 'LLM',
125
124
  error: error instanceof Error ? error.message : String( error )
126
125
  } ) );
127
126
  }
128
- await onFinish?.( parsedResponse );
127
+ await onEnd?.( parsedResponse );
129
128
  } ),
130
129
  onError: event => onErrorHook( event, error => onError?.( { ...event, error } ) )
131
130
  } )
package/src/agent.spec.js CHANGED
@@ -38,7 +38,7 @@ const optionMocks = vi.hoisted( () => ( {
38
38
  const wrapMocks = vi.hoisted( () => ( {
39
39
  wrapGeneration: vi.fn(),
40
40
  wrapStream: vi.fn(),
41
- streamHooks: { onFinishHook: vi.fn(), onErrorHook: vi.fn() }
41
+ streamHooks: { onEndHook: vi.fn(), onErrorHook: vi.fn() }
42
42
  } ) );
43
43
 
44
44
  const streamMocks = vi.hoisted( () => ( {
@@ -113,7 +113,7 @@ const model = { id: 'MODEL' };
113
113
 
114
114
  const textOptions = {
115
115
  model,
116
- system: [ { role: 'system', content: 'You are concise.' } ],
116
+ instructions: [ { role: 'system', content: 'You are concise.' } ],
117
117
  messages: [ { role: 'user', content: 'Initial user message' } ],
118
118
  providerOptions: { test: true },
119
119
  temperature: 0.3
@@ -124,7 +124,7 @@ const assistantMessage = { role: 'assistant', content: 'response' };
124
124
  const aiResponse = {
125
125
  text: 'response',
126
126
  finishReason: 'stop',
127
- response: { messages: [ assistantMessage ] }
127
+ responseMessages: [ assistantMessage ]
128
128
  };
129
129
 
130
130
  describe( 'Agent', () => {
@@ -145,7 +145,7 @@ describe( 'Agent', () => {
145
145
 
146
146
  wrapMocks.wrapGeneration.mockReset().mockImplementation( async ( { fn } ) => fn() );
147
147
  wrapMocks.streamHooks = {
148
- onFinishHook: vi.fn( async ( response, callback ) => callback?.( response ) ),
148
+ onEndHook: vi.fn( async ( response, callback ) => callback?.( response ) ),
149
149
  onErrorHook: vi.fn( ( event, callback ) => callback?.( event.error ) )
150
150
  };
151
151
  wrapMocks.wrapStream.mockReset().mockImplementation( ( { fn } ) => fn( wrapMocks.streamHooks ) );
@@ -255,7 +255,7 @@ describe( 'Agent', () => {
255
255
  };
256
256
  optionMocks.loadAiSdkTextOptions.mockReturnValueOnce( {
257
257
  model,
258
- system: [ systemMessage ],
258
+ instructions: [ systemMessage ],
259
259
  messages: [ { role: 'user', content: 'Hello' } ]
260
260
  } );
261
261
 
@@ -272,7 +272,7 @@ describe( 'Agent', () => {
272
272
  const { Agent } = await importSut();
273
273
  optionMocks.loadAiSdkTextOptions.mockReturnValueOnce( {
274
274
  model,
275
- system: [],
275
+ instructions: [],
276
276
  messages: [ { role: 'user', content: 'Hello' } ]
277
277
  } );
278
278
 
@@ -305,7 +305,7 @@ describe( 'Agent', () => {
305
305
  const { Agent } = await importSut();
306
306
  optionMocks.loadAiSdkTextOptions.mockReturnValueOnce( {
307
307
  model,
308
- system: [ { role: 'system', content: 'You are concise.' } ],
308
+ instructions: [ { role: 'system', content: 'You are concise.' } ],
309
309
  messages: [
310
310
  { role: 'user', content: 'Initial user message' },
311
311
  { role: 'assistant', content: 'Authored assistant block' }
@@ -393,7 +393,7 @@ describe( 'Agent', () => {
393
393
  const response = { ...aiResponse };
394
394
  aiMocks.superStream.mockImplementationOnce( options => {
395
395
  options.onChunk( { chunk } );
396
- options.onFinish( response );
396
+ options.onEnd( response );
397
397
  return stream;
398
398
  } );
399
399
  const { Agent } = await importSut();
@@ -426,7 +426,7 @@ describe( 'Agent', () => {
426
426
  onChunk,
427
427
  abortSignal,
428
428
  toolChoice: 'required',
429
- onFinish: expect.any( Function ),
429
+ onEnd: expect.any( Function ),
430
430
  onError: expect.any( Function )
431
431
  } );
432
432
  expect( streamMocks.drainStream ).toHaveBeenCalledWith( stream, abortSignal );
@@ -441,7 +441,7 @@ describe( 'Agent', () => {
441
441
  it( 'omits onChunk when generateWithStreaming does not receive it', async () => {
442
442
  const stream = { output: Promise.resolve( undefined ) };
443
443
  aiMocks.superStream.mockImplementationOnce( options => {
444
- options.onFinish( { ...aiResponse } );
444
+ options.onEnd( { ...aiResponse } );
445
445
  return stream;
446
446
  } );
447
447
  const { Agent } = await importSut();
@@ -470,14 +470,14 @@ describe( 'Agent', () => {
470
470
  expect( streamMocks.drainStream ).toHaveBeenCalledWith( stream, abortController.signal );
471
471
  } );
472
472
 
473
- it( 'streams with initial, stored, and caller messages and stores on finish', async () => {
473
+ it( 'streams with initial, stored, and caller messages and stores on end', async () => {
474
474
  const store = {
475
475
  getMessages: vi.fn( () => [
476
476
  { role: 'assistant', content: 'Stored reply' }
477
477
  ] ),
478
478
  addMessages: vi.fn()
479
479
  };
480
- const onFinish = vi.fn();
480
+ const onEnd = vi.fn();
481
481
  const onError = vi.fn();
482
482
  const onChunk = vi.fn();
483
483
  const callerMessage = { role: 'user', content: 'New question' };
@@ -486,14 +486,14 @@ describe( 'Agent', () => {
486
486
 
487
487
  const result = await agent.stream( {
488
488
  messages: [ callerMessage ],
489
- onFinish,
489
+ onEnd,
490
490
  onError,
491
491
  onChunk
492
492
  } );
493
493
 
494
494
  expect( validations.parseAgentStreamArgs ).toHaveBeenCalledWith( {
495
495
  messages: [ callerMessage ],
496
- onFinish,
496
+ onEnd,
497
497
  onError,
498
498
  onChunk
499
499
  } );
@@ -510,40 +510,40 @@ describe( 'Agent', () => {
510
510
  ],
511
511
  allowSystemInMessages: true,
512
512
  onChunk,
513
- onFinish: expect.any( Function ),
513
+ onEnd: expect.any( Function ),
514
514
  onError: expect.any( Function )
515
515
  } );
516
516
  const streamOptions = aiMocks.superStream.mock.calls[0][0];
517
- await streamOptions.onFinish( aiResponse );
518
- expect( wrapMocks.streamHooks.onFinishHook ).toHaveBeenCalledWith( aiResponse, expect.any( Function ) );
517
+ await streamOptions.onEnd( aiResponse );
518
+ expect( wrapMocks.streamHooks.onEndHook ).toHaveBeenCalledWith( aiResponse, expect.any( Function ) );
519
519
  expect( store.addMessages ).toHaveBeenCalledWith( [
520
520
  callerMessage,
521
521
  assistantMessage
522
522
  ] );
523
- expect( onFinish ).toHaveBeenCalledWith( aiResponse );
523
+ expect( onEnd ).toHaveBeenCalledWith( aiResponse );
524
524
  expect( result ).toEqual( { textStream: 'stream' } );
525
525
  } );
526
526
 
527
- it( 'logs stream persistence failures and still completes onFinish', async () => {
527
+ it( 'logs stream persistence failures and still completes onEnd', async () => {
528
528
  const persistenceError = new Error( 'Store unavailable' );
529
529
  const store = {
530
530
  getMessages: vi.fn( () => [] ),
531
531
  addMessages: vi.fn().mockRejectedValue( persistenceError )
532
532
  };
533
- const onFinish = vi.fn();
533
+ const onEnd = vi.fn();
534
534
  const { Agent } = await importSut();
535
535
  const agent = new Agent( { prompt: 'test@v1', messageStore: store } );
536
536
 
537
- const result = await agent.stream( { onFinish } );
537
+ const result = await agent.stream( { onEnd } );
538
538
  const streamOptions = aiMocks.superStream.mock.calls[0][0];
539
539
 
540
- await expect( streamOptions.onFinish( aiResponse ) ).resolves.toBeUndefined();
540
+ await expect( streamOptions.onEnd( aiResponse ) ).resolves.toBeUndefined();
541
541
 
542
542
  expect( loggerMocks.error ).toHaveBeenCalledWith(
543
543
  'Agent.stream message store persistence failed',
544
544
  { namespace: 'LLM', error: persistenceError.message }
545
545
  );
546
- expect( onFinish ).toHaveBeenCalledWith( aiResponse );
546
+ expect( onEnd ).toHaveBeenCalledWith( aiResponse );
547
547
  expect( result ).toEqual( { textStream: 'stream' } );
548
548
  } );
549
549
 
@@ -552,16 +552,16 @@ describe( 'Agent', () => {
552
552
  getMessages: vi.fn( () => [] ),
553
553
  addMessages: vi.fn()
554
554
  };
555
- const onFinish = vi.fn();
555
+ const onEnd = vi.fn();
556
556
  const { Agent } = await importSut();
557
557
  const agent = new Agent( { prompt: 'test@v1', messageStore: store } );
558
558
 
559
- await agent.stream( { onFinish } );
559
+ await agent.stream( { onEnd } );
560
560
  const streamOptions = aiMocks.superStream.mock.calls[0][0];
561
- await streamOptions.onFinish( { ...aiResponse, finishReason: 'error' } );
561
+ await streamOptions.onEnd( { ...aiResponse, finishReason: 'error' } );
562
562
 
563
563
  expect( store.addMessages ).not.toHaveBeenCalled();
564
- expect( onFinish ).toHaveBeenCalledWith( { ...aiResponse, finishReason: 'error' } );
564
+ expect( onEnd ).toHaveBeenCalledWith( { ...aiResponse, finishReason: 'error' } );
565
565
  } );
566
566
 
567
567
  it( 'omits onChunk when stream does not receive it', async () => {
@@ -2,7 +2,7 @@ import { loadImageModel, loadTextModel } from './utils/models.js';
2
2
  import { buildLoadSkillTool, loadPromptTools } from './utils/tools.js';
3
3
  import { Role } from './consts.js';
4
4
  import { FatalError } from '@outputai/core';
5
- import { stepCountIs } from 'ai';
5
+ import { isStepCount } from 'ai';
6
6
 
7
7
  const buildSkillsMessageContent = skills =>
8
8
  'Available skills (use load_skill to get full instructions):\n' +
@@ -53,15 +53,15 @@ export const loadAiSdkTextOptions = ( { prompt, tools, skills, stopWhen, output,
53
53
  throw new FatalError( `Prompt "${prompt.name}" has no chat-style messages. Add role-tagged blocks like <system> or <user>.` );
54
54
  }
55
55
 
56
- const systemMessages = prompt.messages.filter( m => m.role === Role.SYSTEM );
57
- const allMessages = prompt.messages.filter( m => m.role !== Role.SYSTEM );
56
+ const instructions = prompt.messages.filter( m => m.role === Role.SYSTEM );
57
+ const messages = prompt.messages.filter( m => m.role !== Role.SYSTEM );
58
58
 
59
59
  if ( skills.length > 0 ) {
60
60
  const skillsMessageContent = buildSkillsMessageContent( skills );
61
- if ( systemMessages.length > 0 ) {
62
- systemMessages[0] = { ...systemMessages[0], content: `${systemMessages[0].content}\n\n${skillsMessageContent}` };
61
+ if ( instructions.length > 0 ) {
62
+ instructions[0] = { ...instructions[0], content: `${instructions[0].content}\n\n${skillsMessageContent}` };
63
63
  } else {
64
- systemMessages.push( { role: Role.SYSTEM, content: skillsMessageContent } );
64
+ instructions.push( { role: Role.SYSTEM, content: skillsMessageContent } );
65
65
  }
66
66
  }
67
67
 
@@ -69,8 +69,8 @@ export const loadAiSdkTextOptions = ( { prompt, tools, skills, stopWhen, output,
69
69
  allowSystemInMessages: true,
70
70
  maxRetries: 0,
71
71
  model: loadTextModel( prompt ),
72
- system: systemMessages,
73
- messages: allMessages,
72
+ instructions,
73
+ messages,
74
74
  ...( output && { output } ),
75
75
  ...( abortSignal && { abortSignal } ),
76
76
  ...( stopWhen && { stopWhen } ),
@@ -87,7 +87,7 @@ export const loadAiSdkTextOptions = ( { prompt, tools, skills, stopWhen, output,
87
87
  options.toolChoice = toolChoice;
88
88
  }
89
89
  if ( !options.stopWhen ) {
90
- options.stopWhen = stepCountIs( prompt.config.maxSteps );
90
+ options.stopWhen = isStepCount( prompt.config.maxSteps );
91
91
  }
92
92
  }
93
93
 
@@ -16,7 +16,7 @@ vi.mock( './utils/tools.js', () => ( {
16
16
  } ) );
17
17
 
18
18
  vi.mock( 'ai', () => ( {
19
- stepCountIs: count => ( { type: 'step-count', count } )
19
+ isStepCount: count => ( { type: 'step-count', count } )
20
20
  } ) );
21
21
 
22
22
  const importSut = async () => import( './ai_sdk_options.js' );
@@ -72,7 +72,7 @@ describe( 'ai_sdk_options', () => {
72
72
  return loadAiSdkTextOptions( { skills: [], ...args } );
73
73
  };
74
74
 
75
- it( 'maps a loaded prompt to model, system, messages, and generation config', async () => {
75
+ it( 'maps a loaded prompt to model, instructions, messages, and generation config', async () => {
76
76
  const prompt = makeTextPrompt( {
77
77
  temperature: 0.3,
78
78
  maxOutputTokens: 1000,
@@ -87,7 +87,7 @@ describe( 'ai_sdk_options', () => {
87
87
  allowSystemInMessages: true,
88
88
  maxRetries: 0,
89
89
  model: 'MODEL',
90
- system: [ { role: 'system', content: 'You are concise.' } ],
90
+ instructions: [ { role: 'system', content: 'You are concise.' } ],
91
91
  messages: [ { role: 'user', content: 'Hello' } ],
92
92
  providerOptions: prompt.config.providerOptions,
93
93
  temperature: 0.3,
@@ -146,7 +146,7 @@ describe( 'ai_sdk_options', () => {
146
146
 
147
147
  const result = await loadText( { prompt } );
148
148
 
149
- expect( result.system ).toEqual( [ {
149
+ expect( result.instructions ).toEqual( [ {
150
150
  role: 'system',
151
151
  content: 'Static',
152
152
  providerOptions
@@ -154,7 +154,7 @@ describe( 'ai_sdk_options', () => {
154
154
  expect( result.messages ).toEqual( [ { role: 'user', content: 'Hello' } ] );
155
155
  } );
156
156
 
157
- it( 'returns an empty system array when the prompt has no system block', async () => {
157
+ it( 'returns an empty instructions array when the prompt has no system block', async () => {
158
158
  const prompt = {
159
159
  name: 'no-system@v1',
160
160
  config: { provider: 'anthropic', model: 'claude-haiku-4-5' },
@@ -164,11 +164,11 @@ describe( 'ai_sdk_options', () => {
164
164
 
165
165
  const result = await loadText( { prompt } );
166
166
 
167
- expect( result.system ).toEqual( [] );
167
+ expect( result.instructions ).toEqual( [] );
168
168
  expect( result.messages ).toEqual( [ { role: 'user', content: 'Hello' } ] );
169
169
  } );
170
170
 
171
- it( 'groups multiple system blocks into system and keeps them out of messages', async () => {
171
+ it( 'groups multiple system blocks into instructions and keeps them out of messages', async () => {
172
172
  const prompt = {
173
173
  name: 'multi-system@v1',
174
174
  config: { provider: 'anthropic', model: 'claude-haiku-4-5' },
@@ -182,7 +182,7 @@ describe( 'ai_sdk_options', () => {
182
182
 
183
183
  const result = await loadText( { prompt } );
184
184
 
185
- expect( result.system ).toEqual( [
185
+ expect( result.instructions ).toEqual( [
186
186
  { role: 'system', content: 'First' },
187
187
  { role: 'system', content: 'Second' }
188
188
  ] );
@@ -235,7 +235,7 @@ describe( 'ai_sdk_options', () => {
235
235
  } );
236
236
 
237
237
  expect( buildLoadSkillToolImpl ).toHaveBeenCalledWith( [ writerSkill ] );
238
- expect( result.system ).toEqual( [
238
+ expect( result.instructions ).toEqual( [
239
239
  { role: 'system', content: `You are concise.\n\n${skillsCatalog}`, providerOptions }
240
240
  ] );
241
241
  expect( result.messages ).toEqual( [ { role: 'user', content: 'Hello' } ] );
@@ -255,7 +255,7 @@ describe( 'ai_sdk_options', () => {
255
255
 
256
256
  const result = await loadText( { prompt, skills: [ writerSkill ] } );
257
257
 
258
- expect( result.system ).toEqual( [ { role: 'system', content: skillsCatalog } ] );
258
+ expect( result.instructions ).toEqual( [ { role: 'system', content: skillsCatalog } ] );
259
259
  expect( result.messages ).toEqual( [ { role: 'user', content: 'Hello' } ] );
260
260
  } );
261
261
 
@@ -0,0 +1,72 @@
1
+ const base64 = 'iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAYAAACqaXHeAAAAfUlEQVR4Xu3VQREAIAwEMWqnD/y7AyGblcDQy+zed8KNB/ADnIANCG+gEaQABShAAQpgMBwFKEABClAgjAAFKEABClCAAhgMRwEKUIACFAgjQAEKUIACFKAABsNRgAIUoAAFwghQgAIUoAAFKIDBcBSgAAUoQIEwAhTIK/ABN/RggaSa6FYAAAAASUVORK5CYII=';
2
+ const bytes = [
3
+ 137, 80, 78, 71, 13, 10, 26, 10, 0, 0, 0, 13, 73, 72, 68, 82, 0, 0, 0, 64, 0, 0, 0, 64,
4
+ 8, 6, 0, 0, 0, 170, 105, 113, 222, 0, 0, 0, 125, 73, 68, 65, 84, 120, 94, 237, 213, 65, 17,
5
+ 0, 32, 12, 4, 49, 106, 167, 15, 252, 187, 3, 33, 155, 149, 192, 208, 203, 236, 222, 119, 194,
6
+ 141, 7, 240, 3, 156, 128, 13, 8, 111, 160, 17, 164, 0, 5, 40, 64, 1, 10, 96, 48, 28, 5, 40,
7
+ 64, 1, 10, 80, 32, 140, 0, 5, 40, 64, 1, 10, 80, 128, 2, 24, 12, 71, 1, 10, 80, 128, 2, 20,
8
+ 8, 35, 64, 1, 10, 80, 128, 2, 20, 160, 0, 6, 195, 81, 128, 2, 20, 160, 0, 5, 194, 8, 80,
9
+ 128, 2, 20, 160, 0, 5, 40, 128, 193, 112, 20, 160, 0, 5, 40, 64, 129, 48, 2, 20, 200, 43,
10
+ 240, 1, 55, 244, 96, 129, 164, 154, 232, 86, 0, 0, 0, 0, 73, 69, 78, 68, 174, 66, 96, 130
11
+ ];
12
+
13
+ export default {
14
+ images: [
15
+ {
16
+ base64Data: base64,
17
+ mediaType: 'image/png',
18
+ // getters
19
+ base64,
20
+ uint8Array: new Uint8Array( bytes )
21
+ }
22
+ ],
23
+ warnings: [
24
+ {
25
+ type: 'unsupported',
26
+ feature: 'size',
27
+ details: 'This model does not support the `size` option. Use `aspectRatio` instead.'
28
+ }
29
+ ],
30
+ responses: [
31
+ {
32
+ timestamp: '2026-08-25T00:00:00.000Z',
33
+ modelId: 'gemini-2.5-flash-image',
34
+ headers: {
35
+ 'alt-svc': 'h3=":443"; ma=2592000,h3-29=":443"; ma=2592000',
36
+ 'content-encoding': 'gzip',
37
+ 'content-type': 'application/json; charset=UTF-8',
38
+ date: 'Tue, 25 Aug 2026 00:00:00 GMT',
39
+ server: 'scaffolding on HTTPServer2',
40
+ 'transfer-encoding': 'chunked',
41
+ vary: 'Origin, X-Origin, Referer',
42
+ 'x-content-type-options': 'nosniff',
43
+ 'x-frame-options': 'SAMEORIGIN',
44
+ 'x-xss-protection': '0'
45
+ }
46
+ }
47
+ ],
48
+ providerMetadata: {
49
+ googleVertex: {
50
+ images: [
51
+ {}
52
+ ]
53
+ },
54
+ vertex: {
55
+ images: [
56
+ {}
57
+ ]
58
+ }
59
+ },
60
+ usage: {
61
+ inputTokens: 157,
62
+ outputTokens: 1290,
63
+ totalTokens: 1447
64
+ },
65
+ image: {
66
+ base64Data: base64,
67
+ mediaType: 'image/png',
68
+ // getters
69
+ base64,
70
+ uint8Array: new Uint8Array( bytes )
71
+ }
72
+ };
@@ -0,0 +1,77 @@
1
+ const base64 = 'iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAYAAACqaXHeAAAAfUlEQVR4Xu3VQREAIAwEMWqnD/y7AyGblcDQy+zed8KNB/ADnIANCG+gEaQABShAAQpgMBwFKEABClAgjAAFKEABClCAAhgMRwEKUIACFAgjQAEKUIACFKAABsNRgAIUoAAFwghQgAIUoAAFKIDBcBSgAAUoQIEwAhTIK/ABN/RggaSa6FYAAAAASUVORK5CYII=';
2
+ const bytes = [
3
+ 137, 80, 78, 71, 13, 10, 26, 10, 0, 0, 0, 13, 73, 72, 68, 82, 0, 0, 0, 64, 0, 0, 0, 64,
4
+ 8, 6, 0, 0, 0, 170, 105, 113, 222, 0, 0, 0, 125, 73, 68, 65, 84, 120, 94, 237, 213, 65, 17,
5
+ 0, 32, 12, 4, 49, 106, 167, 15, 252, 187, 3, 33, 155, 149, 192, 208, 203, 236, 222, 119, 194,
6
+ 141, 7, 240, 3, 156, 128, 13, 8, 111, 160, 17, 164, 0, 5, 40, 64, 1, 10, 96, 48, 28, 5, 40,
7
+ 64, 1, 10, 80, 32, 140, 0, 5, 40, 64, 1, 10, 80, 128, 2, 24, 12, 71, 1, 10, 80, 128, 2, 20,
8
+ 8, 35, 64, 1, 10, 80, 128, 2, 20, 160, 0, 6, 195, 81, 128, 2, 20, 160, 0, 5, 194, 8, 80,
9
+ 128, 2, 20, 160, 0, 5, 40, 128, 193, 112, 20, 160, 0, 5, 40, 64, 129, 48, 2, 20, 200, 43,
10
+ 240, 1, 55, 244, 96, 129, 164, 154, 232, 86, 0, 0, 0, 0, 73, 69, 78, 68, 174, 66, 96, 130
11
+ ];
12
+
13
+ export default {
14
+ images: [
15
+ {
16
+ base64Data: base64,
17
+ mediaType: 'image/png',
18
+ // getters
19
+ base64,
20
+ uint8Array: new Uint8Array( bytes )
21
+ }
22
+ ],
23
+ warnings: [],
24
+ responses: [
25
+ {
26
+ timestamp: '2026-08-25T10:46:40.310Z',
27
+ modelId: 'gpt-image-1',
28
+ headers: {
29
+ 'access-control-expose-headers': 'X-Request-ID, CF-Ray',
30
+ 'alt-svc': 'h3=":443"; ma=86400',
31
+ 'cf-cache-status': 'DYNAMIC',
32
+ 'cf-ray': 'cf-id',
33
+ connection: 'keep-alive',
34
+ 'content-encoding': 'br',
35
+ 'content-type': 'application/json',
36
+ date: 'Tue, 25 Aug 2026 10:47:21 GMT',
37
+ 'openai-organization': 'org-id',
38
+ 'openai-processing-ms': '41337',
39
+ 'openai-project': 'proj-id',
40
+ 'openai-version': '2020-10-01',
41
+ server: 'cloudflare',
42
+ 'set-cookie': '__cf_bm=token; HttpOnly; SameSite=None; Secure; Path=/; Domain=api.openai.com; Expires=Tue, 25 Aug 2026 00:00:00 GMT',
43
+ 'strict-transport-security': 'max-age=31536000; includeSubDomains; preload',
44
+ 'transfer-encoding': 'chunked',
45
+ 'x-content-type-options': 'nosniff',
46
+ 'x-request-id': 'req_id'
47
+ }
48
+ }
49
+ ],
50
+ providerMetadata: {
51
+ openai: {
52
+ images: [
53
+ {
54
+ created: 1787610084,
55
+ size: '1024x1024',
56
+ quality: 'high',
57
+ background: 'opaque',
58
+ outputFormat: 'png',
59
+ imageTokens: 0,
60
+ textTokens: 151
61
+ }
62
+ ]
63
+ }
64
+ },
65
+ usage: {
66
+ inputTokens: 151,
67
+ outputTokens: 4160,
68
+ totalTokens: 4311
69
+ },
70
+ image: {
71
+ base64Data: base64,
72
+ mediaType: 'image/png',
73
+ // getters
74
+ base64,
75
+ uint8Array: new Uint8Array( bytes )
76
+ }
77
+ };