@lobehub/chat 1.64.3 → 1.65.1

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 (72) hide show
  1. package/CHANGELOG.md +58 -0
  2. package/README.md +1 -1
  3. package/changelog/v1.json +21 -0
  4. package/locales/ar/chat.json +7 -1
  5. package/locales/ar/models.json +6 -9
  6. package/locales/bg-BG/chat.json +7 -1
  7. package/locales/bg-BG/models.json +6 -9
  8. package/locales/de-DE/chat.json +7 -1
  9. package/locales/de-DE/models.json +6 -9
  10. package/locales/en-US/chat.json +7 -1
  11. package/locales/en-US/models.json +6 -9
  12. package/locales/es-ES/chat.json +8 -2
  13. package/locales/es-ES/models.json +6 -9
  14. package/locales/fa-IR/chat.json +7 -1
  15. package/locales/fa-IR/models.json +6 -3
  16. package/locales/fr-FR/chat.json +7 -1
  17. package/locales/fr-FR/models.json +6 -9
  18. package/locales/it-IT/chat.json +7 -1
  19. package/locales/it-IT/models.json +6 -9
  20. package/locales/ja-JP/chat.json +7 -1
  21. package/locales/ja-JP/models.json +6 -9
  22. package/locales/ko-KR/chat.json +7 -1
  23. package/locales/ko-KR/models.json +6 -9
  24. package/locales/nl-NL/chat.json +8 -2
  25. package/locales/nl-NL/models.json +6 -9
  26. package/locales/pl-PL/chat.json +7 -1
  27. package/locales/pl-PL/models.json +6 -9
  28. package/locales/pt-BR/chat.json +7 -1
  29. package/locales/pt-BR/models.json +6 -9
  30. package/locales/ru-RU/chat.json +8 -2
  31. package/locales/ru-RU/models.json +6 -9
  32. package/locales/tr-TR/chat.json +7 -1
  33. package/locales/tr-TR/models.json +6 -9
  34. package/locales/vi-VN/chat.json +7 -1
  35. package/locales/vi-VN/models.json +6 -9
  36. package/locales/zh-CN/chat.json +7 -1
  37. package/locales/zh-CN/models.json +6 -9
  38. package/locales/zh-TW/chat.json +7 -1
  39. package/locales/zh-TW/models.json +6 -9
  40. package/package.json +2 -2
  41. package/src/app/(backend)/middleware/auth/index.ts +6 -0
  42. package/src/config/aiModels/anthropic.ts +5 -2
  43. package/src/config/aiModels/google.ts +7 -0
  44. package/src/const/message.ts +3 -0
  45. package/src/const/settings/agent.ts +2 -0
  46. package/src/features/ChatInput/ActionBar/Model/ControlsForm.tsx +38 -13
  47. package/src/features/ChatInput/ActionBar/Model/ReasoningTokenSlider.tsx +92 -0
  48. package/src/features/ChatInput/ActionBar/Model/index.tsx +13 -18
  49. package/src/libs/agent-runtime/anthropic/index.ts +32 -14
  50. package/src/libs/agent-runtime/google/index.test.ts +8 -0
  51. package/src/libs/agent-runtime/google/index.ts +18 -5
  52. package/src/libs/agent-runtime/types/chat.ts +16 -2
  53. package/src/libs/agent-runtime/utils/anthropicHelpers.test.ts +113 -0
  54. package/src/libs/agent-runtime/utils/anthropicHelpers.ts +7 -4
  55. package/src/libs/agent-runtime/utils/streams/anthropic.test.ts +371 -0
  56. package/src/libs/agent-runtime/utils/streams/anthropic.ts +80 -30
  57. package/src/libs/agent-runtime/utils/streams/openai.test.ts +181 -0
  58. package/src/libs/agent-runtime/utils/streams/openai.ts +40 -30
  59. package/src/libs/agent-runtime/utils/streams/protocol.ts +8 -0
  60. package/src/locales/default/chat.ts +7 -1
  61. package/src/services/__tests__/chat.test.ts +89 -50
  62. package/src/services/chat.ts +39 -1
  63. package/src/store/agent/slices/chat/__snapshots__/selectors.test.ts.snap +2 -0
  64. package/src/store/aiInfra/slices/aiModel/selectors.ts +6 -6
  65. package/src/store/chat/slices/aiChat/actions/generateAIChat.ts +1 -1
  66. package/src/store/user/slices/settings/selectors/__snapshots__/settings.test.ts.snap +2 -0
  67. package/src/types/agent/index.ts +23 -9
  68. package/src/types/aiModel.ts +3 -8
  69. package/src/types/message/base.ts +1 -0
  70. package/src/utils/fetch/__tests__/fetchSSE.test.ts +113 -10
  71. package/src/utils/fetch/fetchSSE.ts +12 -3
  72. package/src/features/ChatInput/ActionBar/Model/ExtendControls.tsx +0 -40
@@ -384,6 +384,292 @@ describe('AnthropicStream', () => {
384
384
  expect(onToolCallMock).toHaveBeenCalledTimes(6);
385
385
  });
386
386
 
387
+ describe('thinking', () => {
388
+ it('should handle normal thinking ', async () => {
389
+ const streams = [
390
+ {
391
+ type: 'message_start',
392
+ message: {
393
+ id: 'msg_01MNsLe7n1uVLtu6W8rCFujD',
394
+ type: 'message',
395
+ role: 'assistant',
396
+ model: 'claude-3-7-sonnet-20250219',
397
+ content: [],
398
+ stop_reason: null,
399
+ stop_sequence: null,
400
+ usage: {
401
+ input_tokens: 46,
402
+ cache_creation_input_tokens: 0,
403
+ cache_read_input_tokens: 0,
404
+ output_tokens: 11,
405
+ },
406
+ },
407
+ },
408
+ {
409
+ type: 'content_block_start',
410
+ index: 0,
411
+ content_block: { type: 'thinking', thinking: '', signature: '' },
412
+ },
413
+ {
414
+ type: 'content_block_delta',
415
+ index: 0,
416
+ delta: { type: 'thinking_delta', thinking: '我需要比较两个数字的' },
417
+ },
418
+ {
419
+ type: 'content_block_delta',
420
+ index: 0,
421
+ delta: { type: 'thinking_delta', thinking: '大小:9.8和9' },
422
+ },
423
+ {
424
+ type: 'content_block_delta',
425
+ index: 0,
426
+ delta: { type: 'thinking_delta', thinking: '11\n\n所以9.8比9.11大。' },
427
+ },
428
+ {
429
+ type: 'content_block_delta',
430
+ index: 0,
431
+ delta: {
432
+ type: 'signature_delta',
433
+ signature:
434
+ 'EuYBCkQYAiJAHnHRJG4nPBrdTlo6CmXoyE8WYoQeoPiLnXaeuaM8ExdiIEkVvxK1DYXOz5sCubs2s/G1NsST8A003Zb8XmuhYBIMwDGMZSZ3+gxOEBpVGgzdpOlDNBTxke31SngiMKUk6WcSiA11OSVBuInNukoAhnRd5jPAEg7e5mIoz/qJwnQHV8I+heKUreP77eJdFipQaM3FHn+avEHuLa/Z/fu0O9BftDi+caB1UWDwJakNeWX1yYTvK+N1v4gRpKbj4AhctfYHMjq8qX9XTnXme5AGzCYC6HgYw2/RfalWzwNxI6k=',
435
+ },
436
+ },
437
+ { type: 'content_block_stop', index: 0 },
438
+ { type: 'content_block_start', index: 1, content_block: { type: 'text', text: '' } },
439
+ {
440
+ type: 'content_block_delta',
441
+ index: 1,
442
+ delta: { type: 'text_delta', text: '9.8比9.11大。' },
443
+ },
444
+ { type: 'content_block_stop', index: 1 },
445
+ {
446
+ type: 'message_delta',
447
+ delta: { stop_reason: 'end_turn', stop_sequence: null },
448
+ usage: { output_tokens: 354 },
449
+ },
450
+ { type: 'message_stop' },
451
+ ];
452
+
453
+ const mockReadableStream = new ReadableStream({
454
+ start(controller) {
455
+ streams.forEach((chunk) => {
456
+ controller.enqueue(chunk);
457
+ });
458
+ controller.close();
459
+ },
460
+ });
461
+
462
+ const protocolStream = AnthropicStream(mockReadableStream);
463
+
464
+ const decoder = new TextDecoder();
465
+ const chunks = [];
466
+
467
+ // @ts-ignore
468
+ for await (const chunk of protocolStream) {
469
+ chunks.push(decoder.decode(chunk, { stream: true }));
470
+ }
471
+
472
+ expect(chunks).toEqual(
473
+ [
474
+ 'id: msg_01MNsLe7n1uVLtu6W8rCFujD',
475
+ 'event: data',
476
+ 'data: {"id":"msg_01MNsLe7n1uVLtu6W8rCFujD","type":"message","role":"assistant","model":"claude-3-7-sonnet-20250219","content":[],"stop_reason":null,"stop_sequence":null,"usage":{"input_tokens":46,"cache_creation_input_tokens":0,"cache_read_input_tokens":0,"output_tokens":11}}\n',
477
+ 'id: msg_01MNsLe7n1uVLtu6W8rCFujD',
478
+ 'event: reasoning',
479
+ 'data: ""\n',
480
+ 'id: msg_01MNsLe7n1uVLtu6W8rCFujD',
481
+ 'event: reasoning',
482
+ 'data: "我需要比较两个数字的"\n',
483
+ 'id: msg_01MNsLe7n1uVLtu6W8rCFujD',
484
+ 'event: reasoning',
485
+ 'data: "大小:9.8和9"\n',
486
+ 'id: msg_01MNsLe7n1uVLtu6W8rCFujD',
487
+ 'event: reasoning',
488
+ 'data: "11\\n\\n所以9.8比9.11大。"\n',
489
+ // Tool calls
490
+ 'id: msg_01MNsLe7n1uVLtu6W8rCFujD',
491
+ 'event: reasoning_signature',
492
+ `data: "EuYBCkQYAiJAHnHRJG4nPBrdTlo6CmXoyE8WYoQeoPiLnXaeuaM8ExdiIEkVvxK1DYXOz5sCubs2s/G1NsST8A003Zb8XmuhYBIMwDGMZSZ3+gxOEBpVGgzdpOlDNBTxke31SngiMKUk6WcSiA11OSVBuInNukoAhnRd5jPAEg7e5mIoz/qJwnQHV8I+heKUreP77eJdFipQaM3FHn+avEHuLa/Z/fu0O9BftDi+caB1UWDwJakNeWX1yYTvK+N1v4gRpKbj4AhctfYHMjq8qX9XTnXme5AGzCYC6HgYw2/RfalWzwNxI6k="\n`,
493
+ 'id: msg_01MNsLe7n1uVLtu6W8rCFujD',
494
+ 'event: data',
495
+ `data: {"type":"content_block_stop","index":0}\n`,
496
+ 'id: msg_01MNsLe7n1uVLtu6W8rCFujD',
497
+ 'event: data',
498
+ `data: ""\n`,
499
+ 'id: msg_01MNsLe7n1uVLtu6W8rCFujD',
500
+ 'event: text',
501
+ `data: "9.8比9.11大。"\n`,
502
+ 'id: msg_01MNsLe7n1uVLtu6W8rCFujD',
503
+ 'event: data',
504
+ `data: {"type":"content_block_stop","index":1}\n`,
505
+ 'id: msg_01MNsLe7n1uVLtu6W8rCFujD',
506
+ 'event: stop',
507
+ 'data: "end_turn"\n',
508
+ 'id: msg_01MNsLe7n1uVLtu6W8rCFujD',
509
+ 'event: stop',
510
+ 'data: "message_stop"\n',
511
+ ].map((item) => `${item}\n`),
512
+ );
513
+ });
514
+
515
+ it('should handle flagged thinking ', async () => {
516
+ const streams = [
517
+ {
518
+ type: 'message_start',
519
+ message: {
520
+ id: 'msg_019q32esPvu3TftzZnL6JPys',
521
+ type: 'message',
522
+ role: 'assistant',
523
+ model: 'claude-3-7-sonnet-20250219',
524
+ content: [],
525
+ stop_reason: null,
526
+ stop_sequence: null,
527
+ usage: {
528
+ input_tokens: 92,
529
+ cache_creation_input_tokens: 0,
530
+ cache_read_input_tokens: 0,
531
+ output_tokens: 4,
532
+ },
533
+ },
534
+ },
535
+ {
536
+ type: 'content_block_start',
537
+ index: 0,
538
+ content_block: {
539
+ type: 'redacted_thinking',
540
+ data: 'EvYBCoYBGAIiQNzXoJZW+Ocan2YajVtfm4HE2B3NJdxl05x4M+qDZ2XDAv8uysmma7oaIwNsO/gaZDcaYphIPVvSR0da9BiU4fkqQOseUkmKX3f2PDTFQsTVPGJQdiAoojyYWydq912tQiaWOAnV8pEpsw5qzAhjTg7a/VhucOXRjSO6PrBGUJs4IGgSDEOrVeGKw+XJKwI32RoMXxGUrsCpnzifc238IjCiip27oNxaDKqsGVsa3l8CxznwldGK5o7NKoAWxBr6EjmUyWBfHSjCBSG58dLhH6AqHTHs1h7CpyC9q2PiGFKyI6Qpyq27LMf/IJrL4JzY',
541
+ },
542
+ },
543
+ { type: 'ping' },
544
+ { type: 'content_block_stop', index: 0 },
545
+ {
546
+ type: 'content_block_start',
547
+ index: 1,
548
+ content_block: {
549
+ type: 'redacted_thinking',
550
+ data: 'EqsCCoYBGAIiQFOzsK5wAM+th5SAo3iYCtupF+/ToOYMoKuQowEkQdMYSr+uTiZGV17Ezt1YopNShapyJHraaanqud0SpjNWb1EqQAIs1xKVmShDP/KzTnkeGj3sB1w9fjEcB8I4Q1oYXmAOvEeBRp+/0eszpC5KM4vfBXockGREIX3b9t0aVkKV5LQSDMMox34k4/t6jt5lwBoM8BCR+z8yvwr8RmRAIjCuZUKwzt5cpTSSKsMRF5w/NkH0KeVbDPkHJAHoyKbVThaz2tNP4DGn9Hje/eOhm14qUjEqjkE7ZBa4oXfutU09Ekn6S+Cn5SsYrFLeg+o4/8ewb8YHuspvYbMMN4IwbkqQp19hi2z6QxUWWbLrpMe40Fi2PNKct/dmGmw/SF692L/tyOU=',
551
+ },
552
+ },
553
+ { type: 'content_block_stop', index: 1 },
554
+ {
555
+ type: 'content_block_start',
556
+ index: 2,
557
+ content_block: {
558
+ type: 'redacted_thinking',
559
+ data: 'EowCCoYBGAIiQK1Px08f5EwkoGrjGov2SWq2eHJVrkwBhL9atUCuZegNB+yK0F2ENixvwLlFjZOeSDhfVZ3von76crqoGaEUOUgqQGnTe9FWXAOXYnreuT4sCpUCVSq6pyewyyYCJkAVHTc8YCgPQsGagW9qNmUJDNdCoFyMEtFzqRuHZk3nc/9KjJgSDK4yegsNIw6czWXdCxoMzzrg26MN6RjFUrRqIjAjjEWG7mPPMolxAVvscgcaETILV4WtO4xOXDxK0L2NLSb+GlR7LQraWOATBMBc0lAqM43SvsI2xLX6GvdtNIr98tAKXpadetuHoDta+uqVn9dRfJG6Nno0e1cdx9VzgrOM2I0l6w==',
560
+ },
561
+ },
562
+ { type: 'content_block_stop', index: 2 },
563
+ {
564
+ type: 'content_block_start',
565
+ index: 3,
566
+ content_block: {
567
+ type: 'redacted_thinking',
568
+ data: 'EvMGCoYBGAIiQKkoAFWygajHbTRK/q0hrakXULQBWfg0/EAiNRami4uuzOwDVEPBDu74aP47MMQG0zhLspVkvGpOlfNLkkeROYEqQHO9MLpvtKDkob22tAH2ctP7CxIhI+SRZ0flou71sDdaVtcsel2dIas8+soULHfW68glHJ1ormzeUKv9YHtvVxMSDIC31I3S0nvTPOB/GxoMos7jtbwUPmYvx4viIjD01EiiuBny4srom6xEm/c9VCJQaRKuglEehQ3BRxn2Qs28eGNs7EV63kF5DHV7QTIqmQUaw3A/XKIK+2dhPMzE9/n7VSeWvPl7lFLgTCZBW+q49KoLNuIw5tGMR2nXxTrykvQt9zhNDb9TYAsu8nubMASJt9hWwwMpXAPJhUOP5IL+/p6YDuN9Y5TbDkCiR+3Dgs4xh6VeBhD0cusWdC2LefHT92i1dz2mCFhTtPG8nr/jChOGv/KPPO24sJcSMUYu1T07ohiDCe6vjEckBP2aaSH46rcGEydFBaufPKGD2LsiQfrFDRx639AFlwdeSz30cRrjYCiXBu3l/it0LYt8m5Ixsn41P0xFiPDfecZAkGymvrV8JrS0uPnRbpF9n4CNj1YanoplbVgA9yegj962PnRBHwIoT/UMTLnBgxNE1J9LM6JuMbDQRXpYpZ7OaB9FXwxCKjcWgSiGmiPjdWwan8z7cILDes3Kz9sBaqF4s6uj9eJ31fFL9dHKS0jciCrOPMfKUOQSP/HRuAUsyeyUROquh4MIfXLUUPrFCXyyy42wBvrTXkdWOGZF/wMw6YQGC3iNbgldO4K6OBc8+6+AhRsZR51EuBp1iMl5na6KspyVJnCMx52lUYq3SXNTZkiika/z1jO3C1+cvrzQggo9Yf56bzjKBlVjdjqsIqaNOB8BQqU8EidE668/7cMLF3YJP2YwohEO1C7vOV1vliNkyxdCFz6qB9q8vzZ1hIlFz8LHVxZRmmlMMnAq/Q9nWOXmi/6lIXVRIP+4z6dyIWNINTR/D2ZsMjN34cnDgxgbzGuDoicikliSnJG+RB1smJSAmMrNf+U+JZSW2zpU+7zu1dZm5DMKlef+pmbIJMCxVS7v98vAxt/tO+99HlXwhktL4JuOdC2TcvrDm56e2IeGY0KR5TVA2sfCqxEyb+QAAbwDD7TDwq+r62GVBA==',
569
+ },
570
+ },
571
+ { type: 'content_block_stop', index: 3 },
572
+ { type: 'content_block_start', index: 4, content_block: { type: 'text', text: '' } },
573
+ {
574
+ type: 'content_block_delta',
575
+ index: 4,
576
+ delta: {
577
+ type: 'text_delta',
578
+ text: "I'm not able to respond to special commands or triggers",
579
+ },
580
+ },
581
+ {
582
+ type: 'content_block_delta',
583
+ index: 4,
584
+ delta: {
585
+ type: 'text_delta',
586
+ text: ' with information, answer questions, or assist with many other ways.',
587
+ },
588
+ },
589
+ { type: 'content_block_stop', index: 4 },
590
+ {
591
+ type: 'message_delta',
592
+ delta: { stop_reason: 'end_turn', stop_sequence: null },
593
+ usage: { output_tokens: 259 },
594
+ },
595
+ { type: 'message_stop' },
596
+ ];
597
+
598
+ const mockReadableStream = new ReadableStream({
599
+ start(controller) {
600
+ streams.forEach((chunk) => {
601
+ controller.enqueue(chunk);
602
+ });
603
+ controller.close();
604
+ },
605
+ });
606
+
607
+ const protocolStream = AnthropicStream(mockReadableStream);
608
+
609
+ const decoder = new TextDecoder();
610
+ const chunks = [];
611
+
612
+ // @ts-ignore
613
+ for await (const chunk of protocolStream) {
614
+ chunks.push(decoder.decode(chunk, { stream: true }));
615
+ }
616
+
617
+ expect(chunks).toEqual(
618
+ [
619
+ 'id: msg_019q32esPvu3TftzZnL6JPys',
620
+ 'event: data',
621
+ 'data: {"id":"msg_019q32esPvu3TftzZnL6JPys","type":"message","role":"assistant","model":"claude-3-7-sonnet-20250219","content":[],"stop_reason":null,"stop_sequence":null,"usage":{"input_tokens":92,"cache_creation_input_tokens":0,"cache_read_input_tokens":0,"output_tokens":4}}\n',
622
+ 'id: msg_019q32esPvu3TftzZnL6JPys',
623
+ 'event: flagged_reasoning_signature',
624
+ 'data: "EvYBCoYBGAIiQNzXoJZW+Ocan2YajVtfm4HE2B3NJdxl05x4M+qDZ2XDAv8uysmma7oaIwNsO/gaZDcaYphIPVvSR0da9BiU4fkqQOseUkmKX3f2PDTFQsTVPGJQdiAoojyYWydq912tQiaWOAnV8pEpsw5qzAhjTg7a/VhucOXRjSO6PrBGUJs4IGgSDEOrVeGKw+XJKwI32RoMXxGUrsCpnzifc238IjCiip27oNxaDKqsGVsa3l8CxznwldGK5o7NKoAWxBr6EjmUyWBfHSjCBSG58dLhH6AqHTHs1h7CpyC9q2PiGFKyI6Qpyq27LMf/IJrL4JzY"\n',
625
+ 'id: msg_019q32esPvu3TftzZnL6JPys',
626
+ 'event: data',
627
+ 'data: {"type":"ping"}\n',
628
+ 'id: msg_019q32esPvu3TftzZnL6JPys',
629
+ 'event: data',
630
+ 'data: {"type":"content_block_stop","index":0}\n',
631
+ 'id: msg_019q32esPvu3TftzZnL6JPys',
632
+ 'event: flagged_reasoning_signature',
633
+ 'data: "EqsCCoYBGAIiQFOzsK5wAM+th5SAo3iYCtupF+/ToOYMoKuQowEkQdMYSr+uTiZGV17Ezt1YopNShapyJHraaanqud0SpjNWb1EqQAIs1xKVmShDP/KzTnkeGj3sB1w9fjEcB8I4Q1oYXmAOvEeBRp+/0eszpC5KM4vfBXockGREIX3b9t0aVkKV5LQSDMMox34k4/t6jt5lwBoM8BCR+z8yvwr8RmRAIjCuZUKwzt5cpTSSKsMRF5w/NkH0KeVbDPkHJAHoyKbVThaz2tNP4DGn9Hje/eOhm14qUjEqjkE7ZBa4oXfutU09Ekn6S+Cn5SsYrFLeg+o4/8ewb8YHuspvYbMMN4IwbkqQp19hi2z6QxUWWbLrpMe40Fi2PNKct/dmGmw/SF692L/tyOU="\n',
634
+ // Tool calls
635
+ 'id: msg_019q32esPvu3TftzZnL6JPys',
636
+ 'event: data',
637
+ `data: {"type":"content_block_stop","index":1}\n`,
638
+ 'id: msg_019q32esPvu3TftzZnL6JPys',
639
+ 'event: flagged_reasoning_signature',
640
+ `data: "EowCCoYBGAIiQK1Px08f5EwkoGrjGov2SWq2eHJVrkwBhL9atUCuZegNB+yK0F2ENixvwLlFjZOeSDhfVZ3von76crqoGaEUOUgqQGnTe9FWXAOXYnreuT4sCpUCVSq6pyewyyYCJkAVHTc8YCgPQsGagW9qNmUJDNdCoFyMEtFzqRuHZk3nc/9KjJgSDK4yegsNIw6czWXdCxoMzzrg26MN6RjFUrRqIjAjjEWG7mPPMolxAVvscgcaETILV4WtO4xOXDxK0L2NLSb+GlR7LQraWOATBMBc0lAqM43SvsI2xLX6GvdtNIr98tAKXpadetuHoDta+uqVn9dRfJG6Nno0e1cdx9VzgrOM2I0l6w=="\n`,
641
+ 'id: msg_019q32esPvu3TftzZnL6JPys',
642
+ 'event: data',
643
+ `data: {"type":"content_block_stop","index":2}\n`,
644
+ 'id: msg_019q32esPvu3TftzZnL6JPys',
645
+ 'event: flagged_reasoning_signature',
646
+ `data: "EvMGCoYBGAIiQKkoAFWygajHbTRK/q0hrakXULQBWfg0/EAiNRami4uuzOwDVEPBDu74aP47MMQG0zhLspVkvGpOlfNLkkeROYEqQHO9MLpvtKDkob22tAH2ctP7CxIhI+SRZ0flou71sDdaVtcsel2dIas8+soULHfW68glHJ1ormzeUKv9YHtvVxMSDIC31I3S0nvTPOB/GxoMos7jtbwUPmYvx4viIjD01EiiuBny4srom6xEm/c9VCJQaRKuglEehQ3BRxn2Qs28eGNs7EV63kF5DHV7QTIqmQUaw3A/XKIK+2dhPMzE9/n7VSeWvPl7lFLgTCZBW+q49KoLNuIw5tGMR2nXxTrykvQt9zhNDb9TYAsu8nubMASJt9hWwwMpXAPJhUOP5IL+/p6YDuN9Y5TbDkCiR+3Dgs4xh6VeBhD0cusWdC2LefHT92i1dz2mCFhTtPG8nr/jChOGv/KPPO24sJcSMUYu1T07ohiDCe6vjEckBP2aaSH46rcGEydFBaufPKGD2LsiQfrFDRx639AFlwdeSz30cRrjYCiXBu3l/it0LYt8m5Ixsn41P0xFiPDfecZAkGymvrV8JrS0uPnRbpF9n4CNj1YanoplbVgA9yegj962PnRBHwIoT/UMTLnBgxNE1J9LM6JuMbDQRXpYpZ7OaB9FXwxCKjcWgSiGmiPjdWwan8z7cILDes3Kz9sBaqF4s6uj9eJ31fFL9dHKS0jciCrOPMfKUOQSP/HRuAUsyeyUROquh4MIfXLUUPrFCXyyy42wBvrTXkdWOGZF/wMw6YQGC3iNbgldO4K6OBc8+6+AhRsZR51EuBp1iMl5na6KspyVJnCMx52lUYq3SXNTZkiika/z1jO3C1+cvrzQggo9Yf56bzjKBlVjdjqsIqaNOB8BQqU8EidE668/7cMLF3YJP2YwohEO1C7vOV1vliNkyxdCFz6qB9q8vzZ1hIlFz8LHVxZRmmlMMnAq/Q9nWOXmi/6lIXVRIP+4z6dyIWNINTR/D2ZsMjN34cnDgxgbzGuDoicikliSnJG+RB1smJSAmMrNf+U+JZSW2zpU+7zu1dZm5DMKlef+pmbIJMCxVS7v98vAxt/tO+99HlXwhktL4JuOdC2TcvrDm56e2IeGY0KR5TVA2sfCqxEyb+QAAbwDD7TDwq+r62GVBA=="\n`,
647
+ 'id: msg_019q32esPvu3TftzZnL6JPys',
648
+ 'event: data',
649
+ `data: {"type":"content_block_stop","index":3}\n`,
650
+ 'id: msg_019q32esPvu3TftzZnL6JPys',
651
+ 'event: data',
652
+ `data: ""\n`,
653
+ 'id: msg_019q32esPvu3TftzZnL6JPys',
654
+ 'event: text',
655
+ `data: "I'm not able to respond to special commands or triggers"\n`,
656
+ 'id: msg_019q32esPvu3TftzZnL6JPys',
657
+ 'event: text',
658
+ `data: " with information, answer questions, or assist with many other ways."\n`,
659
+ 'id: msg_019q32esPvu3TftzZnL6JPys',
660
+ 'event: data',
661
+ `data: {"type":"content_block_stop","index":4}\n`,
662
+ 'id: msg_019q32esPvu3TftzZnL6JPys',
663
+ 'event: stop',
664
+ 'data: "end_turn"\n',
665
+ 'id: msg_019q32esPvu3TftzZnL6JPys',
666
+ 'event: stop',
667
+ 'data: "message_stop"\n',
668
+ ].map((item) => `${item}\n`),
669
+ );
670
+ });
671
+ });
672
+
387
673
  it('should handle ReadableStream input', async () => {
388
674
  const mockReadableStream = new ReadableStream({
389
675
  start(controller) {
@@ -424,4 +710,89 @@ describe('AnthropicStream', () => {
424
710
  `data: "message_stop"\n\n`,
425
711
  ]);
426
712
  });
713
+
714
+ it('should handle un-normal block type', async () => {
715
+ const streams = [
716
+ {
717
+ type: 'message_start',
718
+ message: {
719
+ id: 'msg_01MNsLe7n1uVLtu6W8rCFujD',
720
+ type: 'message',
721
+ role: 'assistant',
722
+ model: 'claude-3-7-sonnet-20250219',
723
+ content: [],
724
+ stop_reason: null,
725
+ stop_sequence: null,
726
+ usage: {
727
+ input_tokens: 46,
728
+ cache_creation_input_tokens: 0,
729
+ cache_read_input_tokens: 0,
730
+ output_tokens: 11,
731
+ },
732
+ },
733
+ },
734
+ {
735
+ type: 'content_block_start',
736
+ index: 0,
737
+ content_block: { type: 'thinking', thinking: 'abc', signature: 'dddd' },
738
+ },
739
+ {
740
+ type: 'content_block_start',
741
+ index: 0,
742
+ content_block: { type: 'thinking', thinking: null },
743
+ },
744
+ {
745
+ type: 'content_block_start',
746
+ index: 0,
747
+ content_block: { type: 'abc', abc: '' },
748
+ },
749
+ {
750
+ type: 'content_block_delta',
751
+ index: 0,
752
+ delta: { type: 'abc', abc: '123' },
753
+ },
754
+ ];
755
+
756
+ const mockReadableStream = new ReadableStream({
757
+ start(controller) {
758
+ streams.forEach((chunk) => {
759
+ controller.enqueue(chunk);
760
+ });
761
+ controller.close();
762
+ },
763
+ });
764
+
765
+ const protocolStream = AnthropicStream(mockReadableStream);
766
+
767
+ const decoder = new TextDecoder();
768
+ const chunks = [];
769
+
770
+ // @ts-ignore
771
+ for await (const chunk of protocolStream) {
772
+ chunks.push(decoder.decode(chunk, { stream: true }));
773
+ }
774
+
775
+ expect(chunks).toEqual(
776
+ [
777
+ 'id: msg_01MNsLe7n1uVLtu6W8rCFujD',
778
+ 'event: data',
779
+ 'data: {"id":"msg_01MNsLe7n1uVLtu6W8rCFujD","type":"message","role":"assistant","model":"claude-3-7-sonnet-20250219","content":[],"stop_reason":null,"stop_sequence":null,"usage":{"input_tokens":46,"cache_creation_input_tokens":0,"cache_read_input_tokens":0,"output_tokens":11}}\n',
780
+ 'id: msg_01MNsLe7n1uVLtu6W8rCFujD',
781
+ 'event: reasoning',
782
+ 'data: "abc"\n',
783
+ 'id: msg_01MNsLe7n1uVLtu6W8rCFujD',
784
+ 'event: reasoning_signature',
785
+ 'data: "dddd"\n',
786
+ 'id: msg_01MNsLe7n1uVLtu6W8rCFujD',
787
+ 'event: data',
788
+ 'data: {"type":"thinking","thinking":null}\n',
789
+ 'id: msg_01MNsLe7n1uVLtu6W8rCFujD',
790
+ 'event: data',
791
+ 'data: {"type":"content_block_start","index":0,"content_block":{"type":"abc","abc":""}}\n',
792
+ 'id: msg_01MNsLe7n1uVLtu6W8rCFujD',
793
+ 'event: data',
794
+ 'data: {"type":"content_block_delta","index":0,"delta":{"type":"abc","abc":"123"}}\n',
795
+ ].map((item) => `${item}\n`),
796
+ );
797
+ });
427
798
  });
@@ -14,49 +14,83 @@ import {
14
14
 
15
15
  export const transformAnthropicStream = (
16
16
  chunk: Anthropic.MessageStreamEvent,
17
- stack: StreamContext,
18
- ): StreamProtocolChunk => {
17
+ context: StreamContext,
18
+ ): StreamProtocolChunk | StreamProtocolChunk[] => {
19
19
  // maybe need another structure to add support for multiple choices
20
20
  switch (chunk.type) {
21
21
  case 'message_start': {
22
- stack.id = chunk.message.id;
22
+ context.id = chunk.message.id;
23
23
  return { data: chunk.message, id: chunk.message.id, type: 'data' };
24
24
  }
25
25
  case 'content_block_start': {
26
- if (chunk.content_block.type === 'tool_use') {
27
- const toolChunk = chunk.content_block;
26
+ switch (chunk.content_block.type) {
27
+ case 'redacted_thinking': {
28
+ return {
29
+ data: chunk.content_block.data,
30
+ id: context.id,
31
+ type: 'flagged_reasoning_signature',
32
+ };
33
+ }
28
34
 
29
- // if toolIndex is not defined, set it to 0
30
- if (typeof stack.toolIndex === 'undefined') {
31
- stack.toolIndex = 0;
35
+ case 'text': {
36
+ return { data: chunk.content_block.text, id: context.id, type: 'data' };
32
37
  }
33
- // if toolIndex is defined, increment it
34
- else {
35
- stack.toolIndex += 1;
38
+
39
+ case 'tool_use': {
40
+ const toolChunk = chunk.content_block;
41
+
42
+ // if toolIndex is not defined, set it to 0
43
+ if (typeof context.toolIndex === 'undefined') {
44
+ context.toolIndex = 0;
45
+ }
46
+ // if toolIndex is defined, increment it
47
+ else {
48
+ context.toolIndex += 1;
49
+ }
50
+
51
+ const toolCall: StreamToolCallChunkData = {
52
+ function: {
53
+ arguments: '',
54
+ name: toolChunk.name,
55
+ },
56
+ id: toolChunk.id,
57
+ index: context.toolIndex,
58
+ type: 'function',
59
+ };
60
+
61
+ context.tool = { id: toolChunk.id, index: context.toolIndex, name: toolChunk.name };
62
+
63
+ return { data: [toolCall], id: context.id, type: 'tool_calls' };
36
64
  }
65
+ case 'thinking': {
66
+ const thinkingChunk = chunk.content_block;
67
+
68
+ // if there is signature in the thinking block, return both thinking and signature
69
+ if (!!thinkingChunk.signature) {
70
+ return [
71
+ { data: thinkingChunk.thinking, id: context.id, type: 'reasoning' },
72
+ { data: thinkingChunk.signature, id: context.id, type: 'reasoning_signature' },
73
+ ];
74
+ }
37
75
 
38
- const toolCall: StreamToolCallChunkData = {
39
- function: {
40
- arguments: '',
41
- name: toolChunk.name,
42
- },
43
- id: toolChunk.id,
44
- index: stack.toolIndex,
45
- type: 'function',
46
- };
76
+ if (typeof thinkingChunk.thinking === 'string')
77
+ return { data: thinkingChunk.thinking, id: context.id, type: 'reasoning' };
47
78
 
48
- stack.tool = { id: toolChunk.id, index: stack.toolIndex, name: toolChunk.name };
79
+ return { data: thinkingChunk, id: context.id, type: 'data' };
80
+ }
49
81
 
50
- return { data: [toolCall], id: stack.id, type: 'tool_calls' };
82
+ default: {
83
+ break;
84
+ }
51
85
  }
52
86
 
53
- return { data: chunk.content_block.text, id: stack.id, type: 'data' };
87
+ return { data: chunk, id: context.id, type: 'data' };
54
88
  }
55
89
 
56
90
  case 'content_block_delta': {
57
91
  switch (chunk.delta.type) {
58
92
  case 'text_delta': {
59
- return { data: chunk.delta.text, id: stack.id, type: 'text' };
93
+ return { data: chunk.delta.text, id: context.id, type: 'text' };
60
94
  }
61
95
 
62
96
  case 'input_json_delta': {
@@ -64,34 +98,50 @@ export const transformAnthropicStream = (
64
98
 
65
99
  const toolCall: StreamToolCallChunkData = {
66
100
  function: { arguments: delta },
67
- index: stack.toolIndex || 0,
101
+ index: context.toolIndex || 0,
68
102
  type: 'function',
69
103
  };
70
104
 
71
105
  return {
72
106
  data: [toolCall],
73
- id: stack.id,
107
+ id: context.id,
74
108
  type: 'tool_calls',
75
109
  } as StreamProtocolToolCallChunk;
76
110
  }
77
111
 
112
+ case 'signature_delta': {
113
+ return {
114
+ data: chunk.delta.signature,
115
+ id: context.id,
116
+ type: 'reasoning_signature',
117
+ };
118
+ }
119
+
120
+ case 'thinking_delta': {
121
+ return {
122
+ data: chunk.delta.thinking,
123
+ id: context.id,
124
+ type: 'reasoning',
125
+ };
126
+ }
127
+
78
128
  default: {
79
129
  break;
80
130
  }
81
131
  }
82
- return { data: chunk, id: stack.id, type: 'data' };
132
+ return { data: chunk, id: context.id, type: 'data' };
83
133
  }
84
134
 
85
135
  case 'message_delta': {
86
- return { data: chunk.delta.stop_reason, id: stack.id, type: 'stop' };
136
+ return { data: chunk.delta.stop_reason, id: context.id, type: 'stop' };
87
137
  }
88
138
 
89
139
  case 'message_stop': {
90
- return { data: 'message_stop', id: stack.id, type: 'stop' };
140
+ return { data: 'message_stop', id: context.id, type: 'stop' };
91
141
  }
92
142
 
93
143
  default: {
94
- return { data: chunk, id: stack.id, type: 'data' };
144
+ return { data: chunk, id: context.id, type: 'data' };
95
145
  }
96
146
  }
97
147
  };