@@ -41,3 +41,391 @@ describe('user messages', () => {
41
41
expect ( result ) . toEqual ( [ { role : 'user' , content : 'Hello' } ] ) ;
42
42
} ) ;
43
43
} ) ;
44
+
45
+ describe ( 'cache control' , ( ) => {
46
+ it ( 'should pass cache control from system message provider metadata' , ( ) => {
47
+ const result = convertToOpenRouterChatMessages ( [
48
+ {
49
+ role : 'system' ,
50
+ content : 'System prompt' ,
51
+ providerMetadata : {
52
+ anthropic : {
53
+ cacheControl : { type : 'ephemeral' } ,
54
+ } ,
55
+ } ,
56
+ } ,
57
+ ] ) ;
58
+
59
+ expect ( result ) . toEqual ( [
60
+ {
61
+ role : 'system' ,
62
+ content : 'System prompt' ,
63
+ cache_control : { type : 'ephemeral' } ,
64
+ } ,
65
+ ] ) ;
66
+ } ) ;
67
+
68
+ it ( 'should pass cache control from user message provider metadata (single text part)' , ( ) => {
69
+ const result = convertToOpenRouterChatMessages ( [
70
+ {
71
+ role : 'user' ,
72
+ content : [ { type : 'text' , text : 'Hello' } ] ,
73
+ providerMetadata : {
74
+ anthropic : {
75
+ cacheControl : { type : 'ephemeral' } ,
76
+ } ,
77
+ } ,
78
+ } ,
79
+ ] ) ;
80
+
81
+ expect ( result ) . toEqual ( [
82
+ {
83
+ role : 'user' ,
84
+ content : 'Hello' ,
85
+ cache_control : { type : 'ephemeral' } ,
86
+ } ,
87
+ ] ) ;
88
+ } ) ;
89
+
90
+ it ( 'should pass cache control from user message provider metadata (multiple parts)' , ( ) => {
91
+ const result = convertToOpenRouterChatMessages ( [
92
+ {
93
+ role : 'user' ,
94
+ content : [
95
+ { type : 'text' , text : 'Hello' } ,
96
+ {
97
+ type : 'image' ,
98
+ image : new Uint8Array ( [ 0 , 1 , 2 , 3 ] ) ,
99
+ mimeType : 'image/png' ,
100
+ } ,
101
+ ] ,
102
+ providerMetadata : {
103
+ anthropic : {
104
+ cacheControl : { type : 'ephemeral' } ,
105
+ } ,
106
+ } ,
107
+ } ,
108
+ ] ) ;
109
+
110
+ expect ( result ) . toEqual ( [
111
+ {
112
+ role : 'user' ,
113
+ content : [
114
+ {
115
+ type : 'text' ,
116
+ text : 'Hello' ,
117
+ cache_control : undefined ,
118
+ } ,
119
+ {
120
+ type : 'image_url' ,
121
+ image_url : { url : 'data:image/png;base64,AAECAw==' } ,
122
+ cache_control : { type : 'ephemeral' } ,
123
+ } ,
124
+ ] ,
125
+ } ,
126
+ ] ) ;
127
+ } ) ;
128
+
129
+ it ( 'should pass cache control to multiple image parts from user message provider metadata' , ( ) => {
130
+ const result = convertToOpenRouterChatMessages ( [
131
+ {
132
+ role : 'user' ,
133
+ content : [
134
+ { type : 'text' , text : 'Hello' } ,
135
+ {
136
+ type : 'image' ,
137
+ image : new Uint8Array ( [ 0 , 1 , 2 , 3 ] ) ,
138
+ mimeType : 'image/png' ,
139
+ } ,
140
+ {
141
+ type : 'image' ,
142
+ image : new Uint8Array ( [ 4 , 5 , 6 , 7 ] ) ,
143
+ mimeType : 'image/jpeg' ,
144
+ } ,
145
+ ] ,
146
+ providerMetadata : {
147
+ anthropic : {
148
+ cacheControl : { type : 'ephemeral' } ,
149
+ } ,
150
+ } ,
151
+ } ,
152
+ ] ) ;
153
+
154
+ expect ( result ) . toEqual ( [
155
+ {
156
+ role : 'user' ,
157
+ content : [
158
+ {
159
+ type : 'text' ,
160
+ text : 'Hello' ,
161
+ cache_control : undefined ,
162
+ } ,
163
+ {
164
+ type : 'image_url' ,
165
+ image_url : { url : 'data:image/png;base64,AAECAw==' } ,
166
+ cache_control : { type : 'ephemeral' } ,
167
+ } ,
168
+ {
169
+ type : 'image_url' ,
170
+ image_url : { url : 'data:image/jpeg;base64,BAUGBw==' } ,
171
+ cache_control : { type : 'ephemeral' } ,
172
+ } ,
173
+ ] ,
174
+ } ,
175
+ ] ) ;
176
+ } ) ;
177
+
178
+ it ( 'should pass cache control to file parts from user message provider metadata' , ( ) => {
179
+ const result = convertToOpenRouterChatMessages ( [
180
+ {
181
+ role : 'user' ,
182
+ content : [
183
+ { type : 'text' , text : 'Hello' } ,
184
+ {
185
+ type : 'file' ,
186
+ data : 'file content' ,
187
+ mimeType : 'text/plain' ,
188
+ } ,
189
+ ] ,
190
+ providerMetadata : {
191
+ anthropic : {
192
+ cacheControl : { type : 'ephemeral' } ,
193
+ } ,
194
+ } ,
195
+ } ,
196
+ ] ) ;
197
+
198
+ expect ( result ) . toEqual ( [
199
+ {
200
+ role : 'user' ,
201
+ content : [
202
+ {
203
+ type : 'text' ,
204
+ text : 'Hello' ,
205
+ cache_control : undefined ,
206
+ } ,
207
+ {
208
+ type : 'text' ,
209
+ text : 'file content' ,
210
+ cache_control : { type : 'ephemeral' } ,
211
+ } ,
212
+ ] ,
213
+ } ,
214
+ ] ) ;
215
+ } ) ;
216
+
217
+ it ( 'should handle mixed part-specific and message-level cache control for multiple parts' , ( ) => {
218
+ const result = convertToOpenRouterChatMessages ( [
219
+ {
220
+ role : 'user' ,
221
+ content : [
222
+ {
223
+ type : 'text' ,
224
+ text : 'Hello' ,
225
+ // No part-specific provider metadata
226
+ } ,
227
+ {
228
+ type : 'image' ,
229
+ image : new Uint8Array ( [ 0 , 1 , 2 , 3 ] ) ,
230
+ mimeType : 'image/png' ,
231
+ providerMetadata : {
232
+ anthropic : {
233
+ cacheControl : { type : 'ephemeral' } ,
234
+ } ,
235
+ } ,
236
+ } ,
237
+ {
238
+ type : 'file' ,
239
+ data : 'file content' ,
240
+ mimeType : 'text/plain' ,
241
+ // No part-specific provider metadata
242
+ } ,
243
+ ] ,
244
+ providerMetadata : {
245
+ anthropic : {
246
+ cacheControl : { type : 'ephemeral' } ,
247
+ } ,
248
+ } ,
249
+ } ,
250
+ ] ) ;
251
+
252
+ expect ( result ) . toEqual ( [
253
+ {
254
+ role : 'user' ,
255
+ content : [
256
+ {
257
+ type : 'text' ,
258
+ text : 'Hello' ,
259
+ cache_control : undefined ,
260
+ } ,
261
+ {
262
+ type : 'image_url' ,
263
+ image_url : { url : 'data:image/png;base64,AAECAw==' } ,
264
+ cache_control : { type : 'ephemeral' } ,
265
+ } ,
266
+ {
267
+ type : 'text' ,
268
+ text : 'file content' ,
269
+ cache_control : { type : 'ephemeral' } ,
270
+ } ,
271
+ ] ,
272
+ } ,
273
+ ] ) ;
274
+ } ) ;
275
+
276
+ it ( 'should pass cache control from individual content part provider metadata' , ( ) => {
277
+ const result = convertToOpenRouterChatMessages ( [
278
+ {
279
+ role : 'user' ,
280
+ content : [
281
+ {
282
+ type : 'text' ,
283
+ text : 'Hello' ,
284
+ providerMetadata : {
285
+ anthropic : {
286
+ cacheControl : { type : 'ephemeral' } ,
287
+ } ,
288
+ } ,
289
+ } ,
290
+ {
291
+ type : 'image' ,
292
+ image : new Uint8Array ( [ 0 , 1 , 2 , 3 ] ) ,
293
+ mimeType : 'image/png' ,
294
+ } ,
295
+ ] ,
296
+ } ,
297
+ ] ) ;
298
+
299
+ expect ( result ) . toEqual ( [
300
+ {
301
+ role : 'user' ,
302
+ content : [
303
+ {
304
+ type : 'text' ,
305
+ text : 'Hello' ,
306
+ cache_control : { type : 'ephemeral' } ,
307
+ } ,
308
+ {
309
+ type : 'image_url' ,
310
+ image_url : { url : 'data:image/png;base64,AAECAw==' } ,
311
+ } ,
312
+ ] ,
313
+ } ,
314
+ ] ) ;
315
+ } ) ;
316
+
317
+ it ( 'should pass cache control from assistant message provider metadata' , ( ) => {
318
+ const result = convertToOpenRouterChatMessages ( [
319
+ {
320
+ role : 'assistant' ,
321
+ content : [ { type : 'text' , text : 'Assistant response' } ] ,
322
+ providerMetadata : {
323
+ anthropic : {
324
+ cacheControl : { type : 'ephemeral' } ,
325
+ } ,
326
+ } ,
327
+ } ,
328
+ ] ) ;
329
+
330
+ expect ( result ) . toEqual ( [
331
+ {
332
+ role : 'assistant' ,
333
+ content : 'Assistant response' ,
334
+ cache_control : { type : 'ephemeral' } ,
335
+ } ,
336
+ ] ) ;
337
+ } ) ;
338
+
339
+ it ( 'should pass cache control from tool message provider metadata' , ( ) => {
340
+ const result = convertToOpenRouterChatMessages ( [
341
+ {
342
+ role : 'tool' ,
343
+ content : [
344
+ {
345
+ type : 'tool-result' ,
346
+ toolCallId : 'call-123' ,
347
+ toolName : 'calculator' ,
348
+ result : { answer : 42 } ,
349
+ isError : false ,
350
+ } ,
351
+ ] ,
352
+ providerMetadata : {
353
+ anthropic : {
354
+ cacheControl : { type : 'ephemeral' } ,
355
+ } ,
356
+ } ,
357
+ } ,
358
+ ] ) ;
359
+
360
+ expect ( result ) . toEqual ( [
361
+ {
362
+ role : 'tool' ,
363
+ tool_call_id : 'call-123' ,
364
+ content : JSON . stringify ( { answer : 42 } ) ,
365
+ cache_control : { type : 'ephemeral' } ,
366
+ } ,
367
+ ] ) ;
368
+ } ) ;
369
+
370
+ it ( 'should support the alias cache_control field' , ( ) => {
371
+ const result = convertToOpenRouterChatMessages ( [
372
+ {
373
+ role : 'system' ,
374
+ content : 'System prompt' ,
375
+ providerMetadata : {
376
+ anthropic : {
377
+ cache_control : { type : 'ephemeral' } ,
378
+ } ,
379
+ } ,
380
+ } ,
381
+ ] ) ;
382
+
383
+ expect ( result ) . toEqual ( [
384
+ {
385
+ role : 'system' ,
386
+ content : 'System prompt' ,
387
+ cache_control : { type : 'ephemeral' } ,
388
+ } ,
389
+ ] ) ;
390
+ } ) ;
391
+
392
+ it ( 'should support cache control on last message in content array' , ( ) => {
393
+ const result = convertToOpenRouterChatMessages ( [
394
+ {
395
+ role : 'system' ,
396
+ content : 'System prompt' ,
397
+ } ,
398
+ {
399
+ role : 'user' ,
400
+ content : [
401
+ { type : 'text' , text : 'User prompt' } ,
402
+ {
403
+ type : 'text' ,
404
+ text : 'User prompt 2' ,
405
+ providerMetadata : {
406
+ anthropic : { cacheControl : { type : 'ephemeral' } } ,
407
+ } ,
408
+ } ,
409
+ ] ,
410
+ } ,
411
+ ] ) ;
412
+
413
+ expect ( result ) . toEqual ( [
414
+ {
415
+ role : 'system' ,
416
+ content : 'System prompt' ,
417
+ } ,
418
+ {
419
+ role : 'user' ,
420
+ content : [
421
+ { type : 'text' , text : 'User prompt' } ,
422
+ {
423
+ type : 'text' ,
424
+ text : 'User prompt 2' ,
425
+ cache_control : { type : 'ephemeral' } ,
426
+ } ,
427
+ ] ,
428
+ } ,
429
+ ] ) ;
430
+ } ) ;
431
+ } ) ;
0 commit comments