@lobehub/lobehub 2.0.0-next.88 → 2.0.0-next.89

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 (24) hide show
  1. package/CHANGELOG.md +25 -0
  2. package/changelog/v1.json +9 -0
  3. package/next.config.ts +0 -1
  4. package/package.json +2 -2
  5. package/packages/context-engine/src/processors/ToolCall.ts +1 -0
  6. package/packages/context-engine/src/processors/__tests__/ToolCall.test.ts +59 -0
  7. package/packages/context-engine/src/tools/ToolNameResolver.ts +1 -0
  8. package/packages/context-engine/src/tools/__tests__/ToolNameResolver.test.ts +57 -0
  9. package/packages/context-engine/src/types.ts +1 -0
  10. package/packages/fetch-sse/src/fetchSSE.ts +12 -2
  11. package/packages/model-runtime/src/core/contextBuilders/google.test.ts +479 -0
  12. package/packages/model-runtime/src/core/contextBuilders/google.ts +44 -1
  13. package/packages/model-runtime/src/core/streams/google/google-ai.test.ts +1115 -814
  14. package/packages/model-runtime/src/core/streams/google/index.ts +19 -5
  15. package/packages/model-runtime/src/core/streams/protocol.ts +1 -0
  16. package/packages/model-runtime/src/providers/google/index.test.ts +1 -1
  17. package/packages/model-runtime/src/providers/google/index.ts +11 -9
  18. package/packages/model-runtime/src/types/toolsCalling.ts +3 -1
  19. package/packages/types/src/message/common/tools.ts +3 -0
  20. package/src/features/Conversation/Messages/Group/Error/index.tsx +3 -2
  21. package/src/features/Conversation/Messages/Group/GroupItem.tsx +2 -2
  22. package/src/store/chat/slices/aiChat/actions/streamingExecutor.ts +6 -5
  23. package/src/store/chat/slices/message/actions/optimisticUpdate.ts +6 -11
  24. package/src/store/chat/slices/plugin/actions/internals.ts +2 -2
@@ -6,6 +6,7 @@ import { describe, expect, it, vi } from 'vitest';
6
6
  import { ChatCompletionTool, OpenAIChatMessage, UserMessageContentPart } from '../../types';
7
7
  import { parseDataUri } from '../../utils/uriParser';
8
8
  import {
9
+ GEMINI_MAGIC_THOUGHT_SIGNATURE,
9
10
  buildGoogleMessage,
10
11
  buildGoogleMessages,
11
12
  buildGooglePart,
@@ -232,6 +233,415 @@ describe('google contextBuilders', () => {
232
233
  });
233
234
  });
234
235
 
236
+ it('should correctly convert function call message with thoughtSignature', async () => {
237
+ const message = {
238
+ role: 'assistant',
239
+ tool_calls: [
240
+ {
241
+ function: {
242
+ arguments: JSON.stringify({
243
+ language: ['JSON'],
244
+ path: 'package.json',
245
+ query: '"version":',
246
+ repo: 'lobehub/lobe-chat',
247
+ }),
248
+ name: 'grep____searchGitHub____mcp',
249
+ },
250
+ id: 'grep____searchGitHub____mcp_0_6RnOMTF0',
251
+ thoughtSignature:
252
+ 'EsUHCsIHAdHtim9/MrjP+pnhM8DVkvulyfWQVf+isXQxEAbF32gbflE1hl6Te80qtp77Ywn8opB2uhQOIH/l6SStsj3+XRy1U1DTeKtqZxDBoLP2rNK6pi3/nk0ZOQIc8f6rxB70G/zOhk7d/1XQFqhmw5H+yDVRQjGD1cNPY5ctWGxQLAIk/HMWNovUJzz2c81jGWoXu7k2vtpuur2hcAL+J79BEVUTfvU3mSiXqJFTClmFPB6Fe79i0y3TwM2XdIBxzPgVgf8B+Pnv1S6YDxHNSm46jTlXKcSw30r3ixs5xEOzerbOUW5WG9BGukw/YQVvHiuoGLIALRa2Ig7dlOMH8+o+f0mKJtyYj8yF6wyBMol+G4mhSHvQSKJLj/Z5kFHvDZKeVUEOZed6vZivYLrVezjQPXgLHJMOmbp6QrZGxqW45QxDKY5X5F8giIOM8VgsUYhDQUBown+3vvwkIBA24icDsOwdhJ/roe9GabbGfxpkSzARIFh7rSI01cRKbh6cEaVFXf2WQftPeD7dBseQLiCdUYoy4ytECrjTpknrWnVUG6Ly4SKW6uN/IJXpm9JT9GgnGLIddFtEQzm9sIKWNpGEz6++lZpiCFS6LsYSnTP3vPj/7oSABRmwWywxA8EmLh+sv+jiK5aMjFi1sTuJ0Ujsvza3/SHZKewNi9WKQUDOa9Mqtjs2YGDnJxto4l5GMUzI5vhf6/+/A5eHALfVabaFP97v8FEPrXQU94dognwx4EnNqy/KWmGIlYZYqIfjaSAy7Z74viwl+oTtL9gyyBDc/FrQvXfyrYIq8N0pkLKAEh33fa/+YVocLL1LKI9rb2bg/RRr+Ee4NyIQKhIdEJaEh74d1COd/4r06J92ThkfVo5PEVTSsr8tBKiJ5wSmX9vyhbLWzxmXoq1xfGrs8kg7NMW53XEWGlQrIVOQmUtjjjBQKj6b4rBTAO6EKk63cGFbkSPohifiUBPHbxUUPy/hf0tQpeOo3jA01AuCFLOIZ5IYJ+Rm5+aZTU3Panv+Q7Yl1w5t5swhbNZfg7MlU/sxwLijLuWDDNfw+2Zw/aa3VDPgVw6Nv2vKkHi4tUU0XlgfiQgQYUMPxpGRV837uUxvZFNep2QUlAMog5h4sMYJWIAX1kK1pzsyR/KxuCn6nUq4ovWNBQHLC4aW2ZcGgW/6CbF81F1cewUz+vWNMMkJrL0d9celGEbFuY0Q709UipaDbCg49twlnLV9XUwqC5wYTFBiJbynBDqiZAvXn2YOxNIs8CCzuu2GSCQDo09ksJy5g/o=',
253
+ type: 'function',
254
+ },
255
+ ],
256
+ } as OpenAIChatMessage;
257
+
258
+ const converted = await buildGoogleMessage(message);
259
+
260
+ expect(converted).toEqual({
261
+ parts: [
262
+ {
263
+ functionCall: {
264
+ args: {
265
+ language: ['JSON'],
266
+ path: 'package.json',
267
+ query: '"version":',
268
+ repo: 'lobehub/lobe-chat',
269
+ },
270
+ name: 'grep____searchGitHub____mcp',
271
+ },
272
+ thoughtSignature:
273
+ 'EsUHCsIHAdHtim9/MrjP+pnhM8DVkvulyfWQVf+isXQxEAbF32gbflE1hl6Te80qtp77Ywn8opB2uhQOIH/l6SStsj3+XRy1U1DTeKtqZxDBoLP2rNK6pi3/nk0ZOQIc8f6rxB70G/zOhk7d/1XQFqhmw5H+yDVRQjGD1cNPY5ctWGxQLAIk/HMWNovUJzz2c81jGWoXu7k2vtpuur2hcAL+J79BEVUTfvU3mSiXqJFTClmFPB6Fe79i0y3TwM2XdIBxzPgVgf8B+Pnv1S6YDxHNSm46jTlXKcSw30r3ixs5xEOzerbOUW5WG9BGukw/YQVvHiuoGLIALRa2Ig7dlOMH8+o+f0mKJtyYj8yF6wyBMol+G4mhSHvQSKJLj/Z5kFHvDZKeVUEOZed6vZivYLrVezjQPXgLHJMOmbp6QrZGxqW45QxDKY5X5F8giIOM8VgsUYhDQUBown+3vvwkIBA24icDsOwdhJ/roe9GabbGfxpkSzARIFh7rSI01cRKbh6cEaVFXf2WQftPeD7dBseQLiCdUYoy4ytECrjTpknrWnVUG6Ly4SKW6uN/IJXpm9JT9GgnGLIddFtEQzm9sIKWNpGEz6++lZpiCFS6LsYSnTP3vPj/7oSABRmwWywxA8EmLh+sv+jiK5aMjFi1sTuJ0Ujsvza3/SHZKewNi9WKQUDOa9Mqtjs2YGDnJxto4l5GMUzI5vhf6/+/A5eHALfVabaFP97v8FEPrXQU94dognwx4EnNqy/KWmGIlYZYqIfjaSAy7Z74viwl+oTtL9gyyBDc/FrQvXfyrYIq8N0pkLKAEh33fa/+YVocLL1LKI9rb2bg/RRr+Ee4NyIQKhIdEJaEh74d1COd/4r06J92ThkfVo5PEVTSsr8tBKiJ5wSmX9vyhbLWzxmXoq1xfGrs8kg7NMW53XEWGlQrIVOQmUtjjjBQKj6b4rBTAO6EKk63cGFbkSPohifiUBPHbxUUPy/hf0tQpeOo3jA01AuCFLOIZ5IYJ+Rm5+aZTU3Panv+Q7Yl1w5t5swhbNZfg7MlU/sxwLijLuWDDNfw+2Zw/aa3VDPgVw6Nv2vKkHi4tUU0XlgfiQgQYUMPxpGRV837uUxvZFNep2QUlAMog5h4sMYJWIAX1kK1pzsyR/KxuCn6nUq4ovWNBQHLC4aW2ZcGgW/6CbF81F1cewUz+vWNMMkJrL0d9celGEbFuY0Q709UipaDbCg49twlnLV9XUwqC5wYTFBiJbynBDqiZAvXn2YOxNIs8CCzuu2GSCQDo09ksJy5g/o=',
274
+ },
275
+ ],
276
+ role: 'model',
277
+ });
278
+ });
279
+
280
+ describe('should correctly convert function call message without thoughtSignature', () => {
281
+ it('should add magic signature when last message is tool message', async () => {
282
+ const messages: OpenAIChatMessage[] = [
283
+ {
284
+ content: '<plugins>Web Browsing plugin available</plugins>',
285
+ role: 'system',
286
+ },
287
+ {
288
+ content: '杭州天气如何',
289
+ role: 'user',
290
+ },
291
+ {
292
+ content: '',
293
+ role: 'assistant',
294
+ tool_calls: [
295
+ {
296
+ function: {
297
+ arguments: '{"query":"杭州天气","searchEngines":["google"]}',
298
+ name: 'lobe-web-browsing____search____builtin',
299
+ },
300
+ id: 'call_001',
301
+ type: 'function',
302
+ },
303
+ ],
304
+ },
305
+ {
306
+ content: 'Tool execution was aborted by user.',
307
+ name: 'lobe-web-browsing____search____builtin',
308
+ role: 'tool',
309
+ tool_call_id: 'call_001',
310
+ },
311
+ {
312
+ content: '',
313
+ role: 'assistant',
314
+ tool_calls: [
315
+ {
316
+ function: {
317
+ arguments: '{"query":"杭州 天气","searchEngines":["bing"]}',
318
+ name: 'lobe-web-browsing____search____builtin',
319
+ },
320
+ id: 'call_002',
321
+ type: 'function',
322
+ },
323
+ ],
324
+ },
325
+ {
326
+ content: 'no result',
327
+ name: 'lobe-web-browsing____search____builtin',
328
+ role: 'tool',
329
+ tool_call_id: 'call_002',
330
+ },
331
+ ];
332
+
333
+ const contents = await buildGoogleMessages(messages);
334
+
335
+ expect(contents).toEqual([
336
+ {
337
+ parts: [{ text: '<plugins>Web Browsing plugin available</plugins>' }],
338
+ role: 'user',
339
+ },
340
+ { parts: [{ text: '杭州天气如何' }], role: 'user' },
341
+ {
342
+ parts: [
343
+ {
344
+ functionCall: {
345
+ args: { query: '杭州天气', searchEngines: ['google'] },
346
+ name: 'lobe-web-browsing____search____builtin',
347
+ },
348
+ thoughtSignature: GEMINI_MAGIC_THOUGHT_SIGNATURE,
349
+ },
350
+ ],
351
+ role: 'model',
352
+ },
353
+ {
354
+ parts: [
355
+ {
356
+ functionResponse: {
357
+ name: 'lobe-web-browsing____search____builtin',
358
+ response: { result: 'Tool execution was aborted by user.' },
359
+ },
360
+ },
361
+ ],
362
+ role: 'user',
363
+ },
364
+ {
365
+ parts: [
366
+ {
367
+ functionCall: {
368
+ args: { query: '杭州 天气', searchEngines: ['bing'] },
369
+ name: 'lobe-web-browsing____search____builtin',
370
+ },
371
+ thoughtSignature: GEMINI_MAGIC_THOUGHT_SIGNATURE,
372
+ },
373
+ ],
374
+ role: 'model',
375
+ },
376
+ {
377
+ parts: [
378
+ {
379
+ functionResponse: {
380
+ name: 'lobe-web-browsing____search____builtin',
381
+ response: { result: 'no result' },
382
+ },
383
+ },
384
+ ],
385
+ role: 'user',
386
+ },
387
+ ]);
388
+ });
389
+
390
+ it('should NOT add magic signature when thoughtSignature already exists', async () => {
391
+ const existingSignature = 'existing_signature_from_model';
392
+ const messages: OpenAIChatMessage[] = [
393
+ {
394
+ content: '杭州天气如何',
395
+ role: 'user',
396
+ },
397
+ {
398
+ content: '',
399
+ role: 'assistant',
400
+ tool_calls: [
401
+ {
402
+ function: {
403
+ arguments: '{"query":"杭州天气","searchEngines":["google"]}',
404
+ name: 'lobe-web-browsing____search____builtin',
405
+ },
406
+ id: 'call_001',
407
+ thoughtSignature: existingSignature,
408
+ type: 'function',
409
+ },
410
+ ],
411
+ },
412
+ {
413
+ content: 'Tool result',
414
+ name: 'lobe-web-browsing____search____builtin',
415
+ role: 'tool',
416
+ tool_call_id: 'call_001',
417
+ },
418
+ ];
419
+
420
+ const contents = await buildGoogleMessages(messages);
421
+
422
+ expect(contents).toEqual([
423
+ {
424
+ parts: [{ text: '杭州天气如何' }],
425
+ role: 'user',
426
+ },
427
+ {
428
+ parts: [
429
+ {
430
+ functionCall: {
431
+ args: { query: '杭州天气', searchEngines: ['google'] },
432
+ name: 'lobe-web-browsing____search____builtin',
433
+ },
434
+ // Should keep existing thoughtSignature, not add magic signature
435
+ thoughtSignature: existingSignature,
436
+ },
437
+ ],
438
+ role: 'model',
439
+ },
440
+ {
441
+ parts: [
442
+ {
443
+ functionResponse: {
444
+ name: 'lobe-web-browsing____search____builtin',
445
+ response: { result: 'Tool result' },
446
+ },
447
+ },
448
+ ],
449
+ role: 'user',
450
+ },
451
+ ]);
452
+ });
453
+
454
+ it('should add magic signature only after last user message in multi-turn scenario', async () => {
455
+ const messages: OpenAIChatMessage[] = [
456
+ {
457
+ content: 'First question',
458
+ role: 'user',
459
+ },
460
+ {
461
+ content: '',
462
+ role: 'assistant',
463
+ tool_calls: [
464
+ {
465
+ function: {
466
+ arguments: '{"query":"first"}',
467
+ name: 'search',
468
+ },
469
+ id: 'call_001',
470
+ type: 'function',
471
+ },
472
+ ],
473
+ },
474
+ {
475
+ content: 'First result',
476
+ name: 'search',
477
+ role: 'tool',
478
+ tool_call_id: 'call_001',
479
+ },
480
+ {
481
+ content: 'Second question',
482
+ role: 'user',
483
+ },
484
+ {
485
+ content: '',
486
+ role: 'assistant',
487
+ tool_calls: [
488
+ {
489
+ function: {
490
+ arguments: '{"query":"second"}',
491
+ name: 'search',
492
+ },
493
+ id: 'call_002',
494
+ type: 'function',
495
+ },
496
+ ],
497
+ },
498
+ {
499
+ content: 'Second result',
500
+ name: 'search',
501
+ role: 'tool',
502
+ tool_call_id: 'call_002',
503
+ },
504
+ ];
505
+
506
+ const contents = await buildGoogleMessages(messages);
507
+
508
+ expect(contents).toEqual([
509
+ {
510
+ parts: [{ text: 'First question' }],
511
+ role: 'user',
512
+ },
513
+ {
514
+ parts: [
515
+ {
516
+ functionCall: {
517
+ args: { query: 'first' },
518
+ name: 'search',
519
+ },
520
+ // No magic signature for this one (before last user message)
521
+ },
522
+ ],
523
+ role: 'model',
524
+ },
525
+ {
526
+ parts: [
527
+ {
528
+ functionResponse: {
529
+ name: 'search',
530
+ response: { result: 'First result' },
531
+ },
532
+ },
533
+ ],
534
+ role: 'user',
535
+ },
536
+ {
537
+ parts: [{ text: 'Second question' }],
538
+ role: 'user',
539
+ },
540
+ {
541
+ parts: [
542
+ {
543
+ functionCall: {
544
+ args: { query: 'second' },
545
+ name: 'search',
546
+ },
547
+ // Magic signature added (after last user message)
548
+ thoughtSignature: GEMINI_MAGIC_THOUGHT_SIGNATURE,
549
+ },
550
+ ],
551
+ role: 'model',
552
+ },
553
+ {
554
+ parts: [
555
+ {
556
+ functionResponse: {
557
+ name: 'search',
558
+ response: { result: 'Second result' },
559
+ },
560
+ },
561
+ ],
562
+ role: 'user',
563
+ },
564
+ ]);
565
+ });
566
+
567
+ it('should NOT add magic signature when last message is user text message', async () => {
568
+ const messages: OpenAIChatMessage[] = [
569
+ {
570
+ content: '<plugins>Web Browsing plugin available</plugins>',
571
+ role: 'system',
572
+ },
573
+ {
574
+ content: '杭州天气如何',
575
+ role: 'user',
576
+ },
577
+ {
578
+ content: '',
579
+ role: 'assistant',
580
+ tool_calls: [
581
+ {
582
+ function: {
583
+ arguments: '{"query":"杭州天气","searchEngines":["google"]}',
584
+ name: 'lobe-web-browsing____search____builtin',
585
+ },
586
+ id: 'call_001',
587
+ type: 'function',
588
+ },
589
+ ],
590
+ },
591
+ {
592
+ content: 'Tool execution was aborted by user.',
593
+ name: 'lobe-web-browsing____search____builtin',
594
+ role: 'tool',
595
+ tool_call_id: 'call_001',
596
+ },
597
+ {
598
+ content: 'Please try again',
599
+ role: 'user',
600
+ },
601
+ ];
602
+
603
+ const contents = await buildGoogleMessages(messages);
604
+
605
+ expect(contents).toEqual([
606
+ {
607
+ parts: [{ text: '<plugins>Web Browsing plugin available</plugins>' }],
608
+ role: 'user',
609
+ },
610
+ {
611
+ parts: [{ text: '杭州天气如何' }],
612
+ role: 'user',
613
+ },
614
+ {
615
+ parts: [
616
+ {
617
+ functionCall: {
618
+ args: { query: '杭州天气', searchEngines: ['google'] },
619
+ name: 'lobe-web-browsing____search____builtin',
620
+ },
621
+ // No thoughtSignature should be added when last message is user text
622
+ },
623
+ ],
624
+ role: 'model',
625
+ },
626
+ {
627
+ parts: [
628
+ {
629
+ functionResponse: {
630
+ name: 'lobe-web-browsing____search____builtin',
631
+ response: { result: 'Tool execution was aborted by user.' },
632
+ },
633
+ },
634
+ ],
635
+ role: 'user',
636
+ },
637
+ {
638
+ parts: [{ text: 'Please try again' }],
639
+ role: 'user',
640
+ },
641
+ ]);
642
+ });
643
+ });
644
+
235
645
  it('should correctly handle empty content', async () => {
236
646
  const message: OpenAIChatMessage = {
237
647
  content: '' as any, // explicitly set as empty string
@@ -361,6 +771,7 @@ describe('google contextBuilders', () => {
361
771
  args: { location: 'London', unit: 'celsius' },
362
772
  name: 'get_current_weather',
363
773
  },
774
+ thoughtSignature: GEMINI_MAGIC_THOUGHT_SIGNATURE,
364
775
  },
365
776
  ],
366
777
  role: 'model',
@@ -410,6 +821,74 @@ describe('google contextBuilders', () => {
410
821
  { parts: [{ text: 'Hi' }], role: 'model' },
411
822
  ]);
412
823
  });
824
+
825
+ it('should correctly convert full conversation with thoughtSignature', async () => {
826
+ const messages: OpenAIChatMessage[] = [
827
+ { content: 'system prompt', role: 'system' },
828
+ { content: 'LobeChat 最新版本', role: 'user' },
829
+ {
830
+ content: '',
831
+ role: 'assistant',
832
+ tool_calls: [
833
+ {
834
+ function: {
835
+ arguments: JSON.stringify({
836
+ language: ['JSON'],
837
+ path: 'package.json',
838
+ query: '"version":',
839
+ repo: 'lobehub/lobe-chat',
840
+ }),
841
+ name: 'grep____searchGitHub____mcp',
842
+ },
843
+ id: 'grep____searchGitHub____mcp_0_6RnOMTF0',
844
+ thoughtSignature: 'test-signature',
845
+ type: 'function',
846
+ },
847
+ ],
848
+ },
849
+ {
850
+ content: '',
851
+ name: 'grep____searchGitHub____mcp',
852
+ role: 'tool',
853
+ tool_call_id: 'grep____searchGitHub____mcp_0_6RnOMTF0',
854
+ },
855
+ ];
856
+
857
+ const contents = await buildGoogleMessages(messages);
858
+
859
+ expect(contents).toEqual([
860
+ { parts: [{ text: 'system prompt' }], role: 'user' },
861
+ { parts: [{ text: 'LobeChat 最新版本' }], role: 'user' },
862
+ {
863
+ parts: [
864
+ {
865
+ functionCall: {
866
+ args: {
867
+ language: ['JSON'],
868
+ path: 'package.json',
869
+ query: '"version":',
870
+ repo: 'lobehub/lobe-chat',
871
+ },
872
+ name: 'grep____searchGitHub____mcp',
873
+ },
874
+ thoughtSignature: 'test-signature',
875
+ },
876
+ ],
877
+ role: 'model',
878
+ },
879
+ {
880
+ parts: [
881
+ {
882
+ functionResponse: {
883
+ name: 'grep____searchGitHub____mcp',
884
+ response: { result: '' },
885
+ },
886
+ },
887
+ ],
888
+ role: 'user',
889
+ },
890
+ ]);
891
+ });
413
892
  });
414
893
 
415
894
  describe('buildGoogleTool', () => {
@@ -11,6 +11,12 @@ import { ChatCompletionTool, OpenAIChatMessage, UserMessageContentPart } from '.
11
11
  import { safeParseJSON } from '../../utils/safeParseJSON';
12
12
  import { parseDataUri } from '../../utils/uriParser';
13
13
 
14
+ /**
15
+ * Magic thoughtSignature
16
+ * @see https://ai.google.dev/gemini-api/docs/thought-signatures#model-behavior:~:text=context_engineering_is_the_way_to_go
17
+ */
18
+ export const GEMINI_MAGIC_THOUGHT_SIGNATURE = 'context_engineering_is_the_way_to_go';
19
+
14
20
  /**
15
21
  * Convert OpenAI content part to Google Part format
16
22
  */
@@ -95,6 +101,7 @@ export const buildGoogleMessage = async (
95
101
  args: safeParseJSON(tool.function.arguments)!,
96
102
  name: tool.function.name,
97
103
  },
104
+ thoughtSignature: tool.thoughtSignature,
98
105
  })),
99
106
  role: 'model',
100
107
  };
@@ -155,7 +162,43 @@ export const buildGoogleMessages = async (messages: OpenAIChatMessage[]): Promis
155
162
  const contents = await Promise.all(pools);
156
163
 
157
164
  // Filter out empty messages: contents.parts must not be empty.
158
- return contents.filter((content: Content) => content.parts && content.parts.length > 0);
165
+ const filteredContents = contents.filter(
166
+ (content: Content) => content.parts && content.parts.length > 0,
167
+ );
168
+
169
+ // Check if the last message is a tool message
170
+ const lastMessage = messages.at(-1);
171
+ const shouldAddMagicSignature = lastMessage?.role === 'tool';
172
+
173
+ if (shouldAddMagicSignature) {
174
+ // Find the last user message index in filtered contents
175
+ let lastUserIndex = -1;
176
+ for (let i = filteredContents.length - 1; i >= 0; i--) {
177
+ if (filteredContents[i].role === 'user') {
178
+ // Skip if it's a functionResponse (tool result)
179
+ const hasFunctionResponse = filteredContents[i].parts?.some((p) => p.functionResponse);
180
+ if (!hasFunctionResponse) {
181
+ lastUserIndex = i;
182
+ break;
183
+ }
184
+ }
185
+ }
186
+
187
+ // Add magic signature to all function calls after last user message that don't have thoughtSignature
188
+ for (let i = lastUserIndex + 1; i < filteredContents.length; i++) {
189
+ const content = filteredContents[i];
190
+ if (content.role === 'model' && content.parts) {
191
+ for (const part of content.parts) {
192
+ if (part.functionCall && !part.thoughtSignature) {
193
+ // Only add magic signature if thoughtSignature doesn't exist
194
+ part.thoughtSignature = GEMINI_MAGIC_THOUGHT_SIGNATURE;
195
+ }
196
+ }
197
+ }
198
+ }
199
+ }
200
+
201
+ return filteredContents;
159
202
  };
160
203
 
161
204
  /**