@outputai/llm 0.7.1-next.2a4105c.0 → 0.7.1-next.5485680.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 +2 -2
- package/src/agent.js +10 -10
- package/src/agent.spec.js +89 -21
- package/src/ai_sdk.js +2 -0
- package/src/ai_sdk.spec.js +4 -0
- package/src/ai_sdk_options.js +11 -1
- package/src/ai_sdk_options.spec.js +42 -4
- package/src/index.d.ts +1 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@outputai/llm",
|
|
3
|
-
"version": "0.7.1-next.
|
|
3
|
+
"version": "0.7.1-next.5485680.0",
|
|
4
4
|
"description": "Framework abstraction to interact with LLM models",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "src/index.js",
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
"gray-matter": "4.0.3",
|
|
14
14
|
"liquidjs": "10.25.7",
|
|
15
15
|
"undici": "8.1.0",
|
|
16
|
-
"@outputai/core": "0.7.1-next.
|
|
16
|
+
"@outputai/core": "0.7.1-next.5485680.0"
|
|
17
17
|
},
|
|
18
18
|
"devDependencies": {
|
|
19
19
|
"ai": "6.0.168",
|
package/src/agent.js
CHANGED
|
@@ -5,7 +5,7 @@ import { loadAiSdkTextOptions } from './ai_sdk_options.js';
|
|
|
5
5
|
import { prepareTextPrompt } from './prompt/prepare_text.js';
|
|
6
6
|
import { startTrace, endTraceWithError } from './utils/trace.js';
|
|
7
7
|
import { wrapTextResponse, wrapStreamOnFinishResponse } from './utils/response_wrappers.js';
|
|
8
|
-
import { ROLE, isRole
|
|
8
|
+
import { ROLE, isRole } from './utils/message.js';
|
|
9
9
|
export { skill } from './prompt/skill.js';
|
|
10
10
|
|
|
11
11
|
export const createMemoryConversationStore = () => {
|
|
@@ -43,16 +43,13 @@ export class Agent extends AIToolLoopAgent {
|
|
|
43
43
|
|
|
44
44
|
const { loadedPrompt, tools } = prepareTextPrompt( { prompt, variables, promptDir: resolvedPromptDir, skills, tools: toolsArg } );
|
|
45
45
|
|
|
46
|
-
const { messages
|
|
47
|
-
|
|
48
|
-
// Extract system messages as `instructions` for the ToolLoopAgent constructor
|
|
49
|
-
// and keep user messages for generate() calls — avoids provider errors
|
|
50
|
-
// with multiple system messages during multi-step tool loops
|
|
51
|
-
const systemContent = allMessages.filter( isRole( ROLE.SYSTEM ) ).map( getContent ).join( '\n\n' );
|
|
46
|
+
const { system, messages, ...constructorOptions } = loadAiSdkTextOptions( loadedPrompt );
|
|
52
47
|
|
|
48
|
+
// loadAiSdkTextOptions routes system blocks to the `system` slot (preserving
|
|
49
|
+
// per-message providerOptions); pass them as the agent's `instructions`.
|
|
53
50
|
super( {
|
|
54
51
|
...constructorOptions,
|
|
55
|
-
...(
|
|
52
|
+
...( system.length > 0 ? { instructions: system } : {} ),
|
|
56
53
|
...( tools ? { tools } : {} ),
|
|
57
54
|
stopWhen: stopWhen ?? stepCountIs( maxSteps ),
|
|
58
55
|
...rest
|
|
@@ -60,7 +57,9 @@ export class Agent extends AIToolLoopAgent {
|
|
|
60
57
|
|
|
61
58
|
this.#prompt = prompt;
|
|
62
59
|
this.#modelId = loadedPrompt.config.model;
|
|
63
|
-
|
|
60
|
+
// `messages` is system-free but may still hold authored <assistant>/<tool>
|
|
61
|
+
// blocks; seed only <user> turns into each generate()/stream() call.
|
|
62
|
+
this.#initialMessages = messages.filter( isRole( ROLE.USER ) );
|
|
64
63
|
this.#store = conversationStore ?? null;
|
|
65
64
|
}
|
|
66
65
|
|
|
@@ -79,7 +78,7 @@ export class Agent extends AIToolLoopAgent {
|
|
|
79
78
|
const traceId = startTrace( { name: 'Agent.generate', prompt: this.#prompt } );
|
|
80
79
|
try {
|
|
81
80
|
const messages = await this.#fetchMessages( userMessages );
|
|
82
|
-
const response = await super.generate( { messages, ...callOptions } );
|
|
81
|
+
const response = await super.generate( { messages, allowSystemInMessages: true, ...callOptions } );
|
|
83
82
|
const wrapped = await wrapTextResponse( { traceId, response, modelId: this.#modelId } );
|
|
84
83
|
await this.#storeMessages( userMessages, wrapped );
|
|
85
84
|
return wrapped;
|
|
@@ -95,6 +94,7 @@ export class Agent extends AIToolLoopAgent {
|
|
|
95
94
|
const messages = await this.#fetchMessages( userMessages );
|
|
96
95
|
return super.stream( {
|
|
97
96
|
messages,
|
|
97
|
+
allowSystemInMessages: true,
|
|
98
98
|
...callOptions,
|
|
99
99
|
...wrapStreamOnFinishResponse( { traceId, modelId: this.#modelId, onFinish } ),
|
|
100
100
|
onError( event ) {
|
package/src/agent.spec.js
CHANGED
|
@@ -86,7 +86,8 @@ vi.mock( './utils/trace.js', () => ( {
|
|
|
86
86
|
|
|
87
87
|
vi.mock( './utils/response_wrappers.js', () => ( {
|
|
88
88
|
wrapTextResponse: ( ...args ) => wrapMocks.wrapTextResponse( ...args ),
|
|
89
|
-
wrapStreamOnFinishResponse: ( ...args ) =>
|
|
89
|
+
wrapStreamOnFinishResponse: ( ...args ) =>
|
|
90
|
+
wrapMocks.wrapStreamOnFinishResponse( ...args )
|
|
90
91
|
} ) );
|
|
91
92
|
|
|
92
93
|
vi.mock( './prompt/skill.js', () => ( {
|
|
@@ -112,7 +113,8 @@ const model = { id: 'MODEL' };
|
|
|
112
113
|
|
|
113
114
|
const textOptions = {
|
|
114
115
|
model,
|
|
115
|
-
|
|
116
|
+
system: [ { role: 'system', content: 'You are concise.' } ],
|
|
117
|
+
messages: [ { role: 'user', content: 'Initial user message' } ],
|
|
116
118
|
providerOptions: { test: true },
|
|
117
119
|
temperature: 0.3
|
|
118
120
|
};
|
|
@@ -131,7 +133,9 @@ describe( 'Agent', () => {
|
|
|
131
133
|
aiMocks.superConstructor.mockReset();
|
|
132
134
|
aiMocks.superGenerate.mockReset().mockResolvedValue( aiResponse );
|
|
133
135
|
aiMocks.superStream.mockReset().mockReturnValue( { textStream: 'stream' } );
|
|
134
|
-
aiMocks.stepCountIs
|
|
136
|
+
aiMocks.stepCountIs
|
|
137
|
+
.mockReset()
|
|
138
|
+
.mockImplementation( count => ( { type: 'step-count', count } ) );
|
|
135
139
|
|
|
136
140
|
promptMocks.prepareTextPrompt.mockReset().mockReturnValue( {
|
|
137
141
|
loadedPrompt,
|
|
@@ -143,7 +147,9 @@ describe( 'Agent', () => {
|
|
|
143
147
|
traceMocks.startTrace.mockReset().mockReturnValue( 'trace-id' );
|
|
144
148
|
traceMocks.endTraceWithError.mockReset();
|
|
145
149
|
|
|
146
|
-
wrapMocks.wrapTextResponse
|
|
150
|
+
wrapMocks.wrapTextResponse
|
|
151
|
+
.mockReset()
|
|
152
|
+
.mockImplementation( async ( { response } ) => response );
|
|
147
153
|
wrapMocks.wrapStreamOnFinishResponse.mockReset().mockReturnValue( {
|
|
148
154
|
onFinish: vi.fn()
|
|
149
155
|
} );
|
|
@@ -175,7 +181,9 @@ describe( 'Agent', () => {
|
|
|
175
181
|
|
|
176
182
|
it( 'prepares the prompt using the resolved invocation dir', async () => {
|
|
177
183
|
const { Agent } = await importSut();
|
|
178
|
-
const skills = [
|
|
184
|
+
const skills = [
|
|
185
|
+
{ name: 'style', description: 'Style', instructions: '# Style' }
|
|
186
|
+
];
|
|
179
187
|
const tools = { search: { description: 'Search' } };
|
|
180
188
|
|
|
181
189
|
new Agent( {
|
|
@@ -199,9 +207,11 @@ describe( 'Agent', () => {
|
|
|
199
207
|
|
|
200
208
|
new Agent( { prompt: 'test@v1', promptDir: '/explicit/prompts' } );
|
|
201
209
|
|
|
202
|
-
expect( promptMocks.prepareTextPrompt ).toHaveBeenCalledWith(
|
|
203
|
-
|
|
204
|
-
|
|
210
|
+
expect( promptMocks.prepareTextPrompt ).toHaveBeenCalledWith(
|
|
211
|
+
expect.objectContaining( {
|
|
212
|
+
promptDir: '/explicit/prompts'
|
|
213
|
+
} )
|
|
214
|
+
);
|
|
205
215
|
} );
|
|
206
216
|
|
|
207
217
|
it( 'constructs ToolLoopAgent with text options, instructions, tools, and default stopWhen', async () => {
|
|
@@ -215,12 +225,34 @@ describe( 'Agent', () => {
|
|
|
215
225
|
model,
|
|
216
226
|
providerOptions: { test: true },
|
|
217
227
|
temperature: 0.3,
|
|
218
|
-
instructions: 'You are concise.',
|
|
228
|
+
instructions: [ { role: 'system', content: 'You are concise.' } ],
|
|
219
229
|
tools: preparedTools,
|
|
220
230
|
stopWhen: { type: 'step-count', count: 10 }
|
|
221
231
|
} );
|
|
222
232
|
} );
|
|
223
233
|
|
|
234
|
+
it( 'preserves per-message providerOptions on system messages passed as instructions', async () => {
|
|
235
|
+
const { Agent } = await importSut();
|
|
236
|
+
const systemMessage = {
|
|
237
|
+
role: 'system',
|
|
238
|
+
content: 'You are concise.',
|
|
239
|
+
providerOptions: { anthropic: { cacheControl: { type: 'ephemeral' } } }
|
|
240
|
+
};
|
|
241
|
+
optionMocks.loadAiSdkTextOptions.mockReturnValueOnce( {
|
|
242
|
+
model,
|
|
243
|
+
system: [ systemMessage ],
|
|
244
|
+
messages: [ { role: 'user', content: 'Hello' } ]
|
|
245
|
+
} );
|
|
246
|
+
|
|
247
|
+
new Agent( { prompt: 'test@v1' } );
|
|
248
|
+
|
|
249
|
+
expect( aiMocks.superConstructor ).toHaveBeenCalledWith(
|
|
250
|
+
expect.objectContaining( {
|
|
251
|
+
instructions: [ systemMessage ]
|
|
252
|
+
} )
|
|
253
|
+
);
|
|
254
|
+
} );
|
|
255
|
+
|
|
224
256
|
it( 'omits tools when prompt preparation returns null tools', async () => {
|
|
225
257
|
const { Agent } = await importSut();
|
|
226
258
|
promptMocks.prepareTextPrompt.mockReturnValueOnce( {
|
|
@@ -234,7 +266,7 @@ describe( 'Agent', () => {
|
|
|
234
266
|
model,
|
|
235
267
|
providerOptions: { test: true },
|
|
236
268
|
temperature: 0.3,
|
|
237
|
-
instructions: 'You are concise.',
|
|
269
|
+
instructions: [ { role: 'system', content: 'You are concise.' } ],
|
|
238
270
|
stopWhen: { type: 'step-count', count: 10 }
|
|
239
271
|
} );
|
|
240
272
|
} );
|
|
@@ -246,9 +278,11 @@ describe( 'Agent', () => {
|
|
|
246
278
|
new Agent( { prompt: 'test@v1', stopWhen } );
|
|
247
279
|
|
|
248
280
|
expect( aiMocks.stepCountIs ).not.toHaveBeenCalled();
|
|
249
|
-
expect( aiMocks.superConstructor ).toHaveBeenCalledWith(
|
|
250
|
-
|
|
251
|
-
|
|
281
|
+
expect( aiMocks.superConstructor ).toHaveBeenCalledWith(
|
|
282
|
+
expect.objectContaining( {
|
|
283
|
+
stopWhen
|
|
284
|
+
} )
|
|
285
|
+
);
|
|
252
286
|
} );
|
|
253
287
|
|
|
254
288
|
it( 'passes custom constructor options through', async () => {
|
|
@@ -256,10 +290,12 @@ describe( 'Agent', () => {
|
|
|
256
290
|
|
|
257
291
|
new Agent( { prompt: 'test@v1', temperature: 0.8, seed: 42 } );
|
|
258
292
|
|
|
259
|
-
expect( aiMocks.superConstructor ).toHaveBeenCalledWith(
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
293
|
+
expect( aiMocks.superConstructor ).toHaveBeenCalledWith(
|
|
294
|
+
expect.objectContaining( {
|
|
295
|
+
temperature: 0.8,
|
|
296
|
+
seed: 42
|
|
297
|
+
} )
|
|
298
|
+
);
|
|
263
299
|
} );
|
|
264
300
|
|
|
265
301
|
it( 'keeps only user prompt messages as initial generate messages', async () => {
|
|
@@ -269,13 +305,36 @@ describe( 'Agent', () => {
|
|
|
269
305
|
await agent.generate();
|
|
270
306
|
|
|
271
307
|
expect( aiMocks.superGenerate ).toHaveBeenCalledWith( {
|
|
272
|
-
messages: [ { role: 'user', content: 'Initial user message' } ]
|
|
308
|
+
messages: [ { role: 'user', content: 'Initial user message' } ],
|
|
309
|
+
allowSystemInMessages: true
|
|
310
|
+
} );
|
|
311
|
+
} );
|
|
312
|
+
|
|
313
|
+
it( 'excludes authored assistant/tool blocks from the initial generate messages', async () => {
|
|
314
|
+
const { Agent } = await importSut();
|
|
315
|
+
optionMocks.loadAiSdkTextOptions.mockReturnValueOnce( {
|
|
316
|
+
model,
|
|
317
|
+
system: [ { role: 'system', content: 'You are concise.' } ],
|
|
318
|
+
messages: [
|
|
319
|
+
{ role: 'user', content: 'Initial user message' },
|
|
320
|
+
{ role: 'assistant', content: 'Authored assistant block' }
|
|
321
|
+
]
|
|
322
|
+
} );
|
|
323
|
+
const agent = new Agent( { prompt: 'test@v1' } );
|
|
324
|
+
|
|
325
|
+
await agent.generate();
|
|
326
|
+
|
|
327
|
+
expect( aiMocks.superGenerate ).toHaveBeenCalledWith( {
|
|
328
|
+
messages: [ { role: 'user', content: 'Initial user message' } ],
|
|
329
|
+
allowSystemInMessages: true
|
|
273
330
|
} );
|
|
274
331
|
} );
|
|
275
332
|
|
|
276
333
|
it( 'combines initial, stored, and caller messages for generate', async () => {
|
|
277
334
|
const store = {
|
|
278
|
-
getMessages: vi.fn( () => [
|
|
335
|
+
getMessages: vi.fn( () => [
|
|
336
|
+
{ role: 'assistant', content: 'Stored reply' }
|
|
337
|
+
] ),
|
|
279
338
|
addMessages: vi.fn()
|
|
280
339
|
};
|
|
281
340
|
const callerMessage = { role: 'user', content: 'New question' };
|
|
@@ -290,6 +349,7 @@ describe( 'Agent', () => {
|
|
|
290
349
|
{ role: 'assistant', content: 'Stored reply' },
|
|
291
350
|
callerMessage
|
|
292
351
|
],
|
|
352
|
+
allowSystemInMessages: true,
|
|
293
353
|
maxRetries: 1
|
|
294
354
|
} );
|
|
295
355
|
} );
|
|
@@ -336,7 +396,9 @@ describe( 'Agent', () => {
|
|
|
336
396
|
|
|
337
397
|
it( 'streams with initial, stored, and caller messages', async () => {
|
|
338
398
|
const store = {
|
|
339
|
-
getMessages: vi.fn( () => [
|
|
399
|
+
getMessages: vi.fn( () => [
|
|
400
|
+
{ role: 'assistant', content: 'Stored reply' }
|
|
401
|
+
] ),
|
|
340
402
|
addMessages: vi.fn()
|
|
341
403
|
};
|
|
342
404
|
const onFinish = vi.fn();
|
|
@@ -345,7 +407,12 @@ describe( 'Agent', () => {
|
|
|
345
407
|
const { Agent } = await importSut();
|
|
346
408
|
const agent = new Agent( { prompt: 'test@v1', conversationStore: store } );
|
|
347
409
|
|
|
348
|
-
const result = await agent.stream( {
|
|
410
|
+
const result = await agent.stream( {
|
|
411
|
+
messages: [ callerMessage ],
|
|
412
|
+
onFinish,
|
|
413
|
+
onError,
|
|
414
|
+
maxRetries: 1
|
|
415
|
+
} );
|
|
349
416
|
|
|
350
417
|
expect( traceMocks.startTrace ).toHaveBeenCalledWith( {
|
|
351
418
|
name: 'Agent.stream',
|
|
@@ -362,6 +429,7 @@ describe( 'Agent', () => {
|
|
|
362
429
|
{ role: 'assistant', content: 'Stored reply' },
|
|
363
430
|
callerMessage
|
|
364
431
|
],
|
|
432
|
+
allowSystemInMessages: true,
|
|
365
433
|
maxRetries: 1,
|
|
366
434
|
onFinish: expect.any( Function ),
|
|
367
435
|
onError: expect.any( Function )
|
package/src/ai_sdk.js
CHANGED
|
@@ -22,6 +22,7 @@ export async function generateText( { prompt, variables, promptDir, skills = [],
|
|
|
22
22
|
try {
|
|
23
23
|
const response = await AI.generateText( {
|
|
24
24
|
...loadAiSdkTextOptions( loadedPrompt ),
|
|
25
|
+
allowSystemInMessages: true,
|
|
25
26
|
maxRetries: 0,
|
|
26
27
|
...aiSdkArgs,
|
|
27
28
|
...( tools && { tools } ),
|
|
@@ -50,6 +51,7 @@ export function streamText( { prompt, variables, promptDir, skills = [], maxStep
|
|
|
50
51
|
try {
|
|
51
52
|
return AI.streamText( {
|
|
52
53
|
...loadAiSdkTextOptions( loadedPrompt ),
|
|
54
|
+
allowSystemInMessages: true,
|
|
53
55
|
maxRetries: 0,
|
|
54
56
|
...aiSdkArgs,
|
|
55
57
|
...( tools && { tools } ),
|
package/src/ai_sdk.spec.js
CHANGED
|
@@ -190,6 +190,7 @@ describe( 'ai_sdk', () => {
|
|
|
190
190
|
expect( aiFns.stepCountIs ).toHaveBeenCalledWith( 4 );
|
|
191
191
|
expect( aiFns.generateText ).toHaveBeenCalledWith( {
|
|
192
192
|
...textOptions,
|
|
193
|
+
allowSystemInMessages: true,
|
|
193
194
|
maxRetries: 0,
|
|
194
195
|
tools,
|
|
195
196
|
temperature: 0.2,
|
|
@@ -225,6 +226,7 @@ describe( 'ai_sdk', () => {
|
|
|
225
226
|
expect( aiFns.stepCountIs ).not.toHaveBeenCalled();
|
|
226
227
|
expect( aiFns.generateText ).toHaveBeenCalledWith( {
|
|
227
228
|
...textOptions,
|
|
229
|
+
allowSystemInMessages: true,
|
|
228
230
|
maxRetries: 0
|
|
229
231
|
} );
|
|
230
232
|
} );
|
|
@@ -326,6 +328,7 @@ describe( 'ai_sdk', () => {
|
|
|
326
328
|
expect( aiFns.stepCountIs ).toHaveBeenCalledWith( 4 );
|
|
327
329
|
expect( aiFns.streamText ).toHaveBeenCalledWith( {
|
|
328
330
|
...textOptions,
|
|
331
|
+
allowSystemInMessages: true,
|
|
329
332
|
maxRetries: 0,
|
|
330
333
|
tools,
|
|
331
334
|
temperature: 0.2,
|
|
@@ -373,6 +376,7 @@ describe( 'ai_sdk', () => {
|
|
|
373
376
|
expect( aiFns.stepCountIs ).not.toHaveBeenCalled();
|
|
374
377
|
expect( aiFns.streamText ).toHaveBeenCalledWith( {
|
|
375
378
|
...textOptions,
|
|
379
|
+
allowSystemInMessages: true,
|
|
376
380
|
maxRetries: 0,
|
|
377
381
|
onFinish: expect.any( Function ),
|
|
378
382
|
onError: expect.any( Function )
|
package/src/ai_sdk_options.js
CHANGED
|
@@ -1,10 +1,16 @@
|
|
|
1
1
|
import { loadImageModel, loadTextModel, loadTools } from './ai_model.js';
|
|
2
2
|
import { resolveMessageProviderOptions } from './prompt/block_options.js';
|
|
3
|
+
import { ROLE, isRole } from './utils/message.js';
|
|
3
4
|
import { FatalError } from '@outputai/core';
|
|
4
5
|
|
|
5
6
|
/**
|
|
6
7
|
* Convert a loaded prompt into AI SDK text generation options.
|
|
7
8
|
*
|
|
9
|
+
* System blocks are routed to the `system` option (as `SystemModelMessage[]`, so
|
|
10
|
+
* per-message providerOptions like `cacheControl` are preserved) rather than left
|
|
11
|
+
* in `messages` — the AI SDK flags system roles inside `messages` as a prompt
|
|
12
|
+
* injection risk, and `system` is the provider-recommended slot.
|
|
13
|
+
*
|
|
8
14
|
* @param {object} prompt - Loaded prompt object
|
|
9
15
|
* @returns {object} Options for AI SDK text calls
|
|
10
16
|
*/
|
|
@@ -12,9 +18,13 @@ export const loadAiSdkTextOptions = prompt => {
|
|
|
12
18
|
if ( prompt.messages.length === 0 ) {
|
|
13
19
|
throw new FatalError( `Prompt "${prompt.name}" has no chat-style messages. Add role-tagged blocks like <system> or <user>.` );
|
|
14
20
|
}
|
|
21
|
+
const isSystem = isRole( ROLE.SYSTEM );
|
|
22
|
+
const resolvedMessages = resolveMessageProviderOptions( prompt );
|
|
23
|
+
|
|
15
24
|
const options = {
|
|
16
25
|
model: loadTextModel( prompt ),
|
|
17
|
-
|
|
26
|
+
system: resolvedMessages.filter( isSystem ),
|
|
27
|
+
messages: resolvedMessages.filter( message => !isSystem( message ) ),
|
|
18
28
|
providerOptions: prompt.config.providerOptions
|
|
19
29
|
};
|
|
20
30
|
|
|
@@ -60,7 +60,8 @@ describe( 'ai_sdk_options', () => {
|
|
|
60
60
|
expect( loadToolsImpl ).toHaveBeenCalledWith( prompt );
|
|
61
61
|
expect( result ).toEqual( {
|
|
62
62
|
model: 'MODEL',
|
|
63
|
-
|
|
63
|
+
system: [ { role: 'system', content: 'You are concise.' } ],
|
|
64
|
+
messages: [ { role: 'user', content: 'Hello' } ],
|
|
64
65
|
providerOptions: prompt.config.providerOptions,
|
|
65
66
|
temperature: 0.3,
|
|
66
67
|
maxOutputTokens: 1000
|
|
@@ -180,13 +181,50 @@ describe( 'ai_sdk_options', () => {
|
|
|
180
181
|
const { loadAiSdkTextOptions } = await importSut();
|
|
181
182
|
const result = loadAiSdkTextOptions( prompt );
|
|
182
183
|
|
|
183
|
-
expect( result.
|
|
184
|
+
expect( result.system ).toEqual( [
|
|
184
185
|
{
|
|
185
186
|
role: 'system',
|
|
186
187
|
content: 'Static',
|
|
187
188
|
providerOptions: { anthropic: { cacheControl: { type: 'ephemeral', ttl: '1h' } } }
|
|
188
|
-
}
|
|
189
|
-
|
|
189
|
+
}
|
|
190
|
+
] );
|
|
191
|
+
expect( result.messages ).toEqual( [ { role: 'user', content: 'Hello' } ] );
|
|
192
|
+
} );
|
|
193
|
+
|
|
194
|
+
it( 'returns an empty system array when the prompt has no system block', async () => {
|
|
195
|
+
const prompt = {
|
|
196
|
+
name: 'no-system@v1',
|
|
197
|
+
config: { provider: 'anthropic', model: 'claude-haiku-4-5' },
|
|
198
|
+
messages: [ { role: 'user', content: 'Hello' } ],
|
|
199
|
+
instructions: null
|
|
200
|
+
};
|
|
201
|
+
|
|
202
|
+
const { loadAiSdkTextOptions } = await importSut();
|
|
203
|
+
const result = loadAiSdkTextOptions( prompt );
|
|
204
|
+
|
|
205
|
+
expect( result.system ).toEqual( [] );
|
|
206
|
+
expect( result.messages ).toEqual( [ { role: 'user', content: 'Hello' } ] );
|
|
207
|
+
} );
|
|
208
|
+
|
|
209
|
+
it( 'groups multiple system blocks into the system option and keeps them out of messages', async () => {
|
|
210
|
+
const prompt = {
|
|
211
|
+
name: 'multi-system@v1',
|
|
212
|
+
config: { provider: 'anthropic', model: 'claude-haiku-4-5' },
|
|
213
|
+
messages: [
|
|
214
|
+
{ role: 'system', content: 'First' },
|
|
215
|
+
{ role: 'system', content: 'Second' },
|
|
216
|
+
{ role: 'user', content: 'Hello' }
|
|
217
|
+
],
|
|
218
|
+
instructions: null
|
|
219
|
+
};
|
|
220
|
+
|
|
221
|
+
const { loadAiSdkTextOptions } = await importSut();
|
|
222
|
+
const result = loadAiSdkTextOptions( prompt );
|
|
223
|
+
|
|
224
|
+
expect( result.system ).toEqual( [
|
|
225
|
+
{ role: 'system', content: 'First' },
|
|
226
|
+
{ role: 'system', content: 'Second' }
|
|
190
227
|
] );
|
|
228
|
+
expect( result.messages ).toEqual( [ { role: 'user', content: 'Hello' } ] );
|
|
191
229
|
} );
|
|
192
230
|
} );
|
package/src/index.d.ts
CHANGED
|
@@ -62,7 +62,7 @@ export type PromptMessage = {
|
|
|
62
62
|
* frontmatter `messageOptions` set names — which is resolved into per-message `providerOptions`
|
|
63
63
|
* at call time and stripped before the request is sent. Authored as `<system options="set_a set_b">`.
|
|
64
64
|
*/
|
|
65
|
-
attributes?: Record<string, string |
|
|
65
|
+
attributes?: Record<string, string | true>;
|
|
66
66
|
};
|
|
67
67
|
|
|
68
68
|
/**
|