@lobehub/chat 1.65.0 → 1.65.2

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 (33) hide show
  1. package/CHANGELOG.md +51 -0
  2. package/changelog/v1.json +18 -0
  3. package/docker-compose/local/docker-compose.yml +14 -0
  4. package/docker-compose/local/searxng-settings.yml +2582 -0
  5. package/docker-compose/setup.sh +3 -1
  6. package/docs/self-hosting/advanced/model-list.mdx +4 -2
  7. package/docs/self-hosting/advanced/model-list.zh-CN.mdx +4 -2
  8. package/package.json +7 -7
  9. package/src/app/(backend)/middleware/auth/index.ts +6 -0
  10. package/src/config/aiModels/google.ts +3 -3
  11. package/src/config/aiModels/groq.ts +10 -0
  12. package/src/config/aiModels/qwen.ts +43 -26
  13. package/src/const/message.ts +3 -0
  14. package/src/features/Conversation/Messages/Assistant/Tool/Render/CustomRender.tsx +7 -7
  15. package/src/features/MobileSwitchLoading/index.tsx +0 -1
  16. package/src/libs/agent-runtime/google/index.test.ts +8 -0
  17. package/src/libs/agent-runtime/google/index.ts +18 -5
  18. package/src/libs/agent-runtime/types/chat.ts +9 -1
  19. package/src/libs/agent-runtime/utils/anthropicHelpers.test.ts +113 -0
  20. package/src/libs/agent-runtime/utils/anthropicHelpers.ts +7 -4
  21. package/src/libs/agent-runtime/utils/streams/anthropic.test.ts +339 -94
  22. package/src/libs/agent-runtime/utils/streams/anthropic.ts +54 -34
  23. package/src/libs/agent-runtime/utils/streams/openai.test.ts +181 -0
  24. package/src/libs/agent-runtime/utils/streams/openai.ts +40 -30
  25. package/src/libs/agent-runtime/utils/streams/protocol.ts +4 -0
  26. package/src/services/__tests__/chat.test.ts +89 -50
  27. package/src/services/chat.ts +13 -1
  28. package/src/store/chat/slices/aiChat/actions/generateAIChat.ts +1 -1
  29. package/src/types/message/base.ts +1 -0
  30. package/src/utils/fetch/__tests__/fetchSSE.test.ts +113 -10
  31. package/src/utils/fetch/fetchSSE.ts +12 -3
  32. package/src/utils/parseModels.test.ts +14 -0
  33. package/src/utils/parseModels.ts +4 -0
@@ -384,7 +384,334 @@ describe('AnthropicStream', () => {
384
384
  expect(onToolCallMock).toHaveBeenCalledTimes(6);
385
385
  });
386
386
 
387
- it('should handle thinking ', async () => {
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
+
673
+ it('should handle ReadableStream input', async () => {
674
+ const mockReadableStream = new ReadableStream({
675
+ start(controller) {
676
+ controller.enqueue({
677
+ type: 'message_start',
678
+ message: { id: 'message_1', metadata: {} },
679
+ });
680
+ controller.enqueue({
681
+ type: 'content_block_delta',
682
+ delta: { type: 'text_delta', text: 'Hello' },
683
+ });
684
+ controller.enqueue({
685
+ type: 'message_stop',
686
+ });
687
+ controller.close();
688
+ },
689
+ });
690
+
691
+ const protocolStream = AnthropicStream(mockReadableStream);
692
+
693
+ const decoder = new TextDecoder();
694
+ const chunks = [];
695
+
696
+ // @ts-ignore
697
+ for await (const chunk of protocolStream) {
698
+ chunks.push(decoder.decode(chunk, { stream: true }));
699
+ }
700
+
701
+ expect(chunks).toEqual([
702
+ 'id: message_1\n',
703
+ 'event: data\n',
704
+ `data: {"id":"message_1","metadata":{}}\n\n`,
705
+ 'id: message_1\n',
706
+ 'event: text\n',
707
+ `data: "Hello"\n\n`,
708
+ 'id: message_1\n',
709
+ 'event: stop\n',
710
+ `data: "message_stop"\n\n`,
711
+ ]);
712
+ });
713
+
714
+ it('should handle un-normal block type', async () => {
388
715
  const streams = [
389
716
  {
390
717
  type: 'message_start',
@@ -407,46 +734,23 @@ describe('AnthropicStream', () => {
407
734
  {
408
735
  type: 'content_block_start',
409
736
  index: 0,
410
- content_block: { type: 'thinking', thinking: '', signature: '' },
411
- },
412
- {
413
- type: 'content_block_delta',
414
- index: 0,
415
- delta: { type: 'thinking_delta', thinking: '我需要比较两个数字的' },
737
+ content_block: { type: 'thinking', thinking: 'abc', signature: 'dddd' },
416
738
  },
417
739
  {
418
- type: 'content_block_delta',
740
+ type: 'content_block_start',
419
741
  index: 0,
420
- delta: { type: 'thinking_delta', thinking: '大小:9.8和9' },
742
+ content_block: { type: 'thinking', thinking: null },
421
743
  },
422
744
  {
423
- type: 'content_block_delta',
745
+ type: 'content_block_start',
424
746
  index: 0,
425
- delta: { type: 'thinking_delta', thinking: '11\n\n所以9.8比9.11大。' },
747
+ content_block: { type: 'abc', abc: '' },
426
748
  },
427
749
  {
428
750
  type: 'content_block_delta',
429
751
  index: 0,
430
- delta: {
431
- type: 'signature_delta',
432
- signature:
433
- 'EuYBCkQYAiJAHnHRJG4nPBrdTlo6CmXoyE8WYoQeoPiLnXaeuaM8ExdiIEkVvxK1DYXOz5sCubs2s/G1NsST8A003Zb8XmuhYBIMwDGMZSZ3+gxOEBpVGgzdpOlDNBTxke31SngiMKUk6WcSiA11OSVBuInNukoAhnRd5jPAEg7e5mIoz/qJwnQHV8I+heKUreP77eJdFipQaM3FHn+avEHuLa/Z/fu0O9BftDi+caB1UWDwJakNeWX1yYTvK+N1v4gRpKbj4AhctfYHMjq8qX9XTnXme5AGzCYC6HgYw2/RfalWzwNxI6k=',
434
- },
435
- },
436
- { type: 'content_block_stop', index: 0 },
437
- { type: 'content_block_start', index: 1, content_block: { type: 'text', text: '' } },
438
- {
439
- type: 'content_block_delta',
440
- index: 1,
441
- delta: { type: 'text_delta', text: '9.8比9.11大。' },
752
+ delta: { type: 'abc', abc: '123' },
442
753
  },
443
- { type: 'content_block_stop', index: 1 },
444
- {
445
- type: 'message_delta',
446
- delta: { stop_reason: 'end_turn', stop_sequence: null },
447
- usage: { output_tokens: 354 },
448
- },
449
- { type: 'message_stop' },
450
754
  ];
451
755
 
452
756
  const mockReadableStream = new ReadableStream({
@@ -475,79 +779,20 @@ describe('AnthropicStream', () => {
475
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',
476
780
  'id: msg_01MNsLe7n1uVLtu6W8rCFujD',
477
781
  'event: reasoning',
478
- 'data: ""\n',
479
- 'id: msg_01MNsLe7n1uVLtu6W8rCFujD',
480
- 'event: reasoning',
481
- 'data: "我需要比较两个数字的"\n',
482
- 'id: msg_01MNsLe7n1uVLtu6W8rCFujD',
483
- 'event: reasoning',
484
- 'data: "大小:9.8和9"\n',
485
- 'id: msg_01MNsLe7n1uVLtu6W8rCFujD',
486
- 'event: reasoning',
487
- 'data: "11\\n\\n所以9.8比9.11大。"\n',
488
- // Tool calls
782
+ 'data: "abc"\n',
489
783
  'id: msg_01MNsLe7n1uVLtu6W8rCFujD',
490
784
  'event: reasoning_signature',
491
- `data: "EuYBCkQYAiJAHnHRJG4nPBrdTlo6CmXoyE8WYoQeoPiLnXaeuaM8ExdiIEkVvxK1DYXOz5sCubs2s/G1NsST8A003Zb8XmuhYBIMwDGMZSZ3+gxOEBpVGgzdpOlDNBTxke31SngiMKUk6WcSiA11OSVBuInNukoAhnRd5jPAEg7e5mIoz/qJwnQHV8I+heKUreP77eJdFipQaM3FHn+avEHuLa/Z/fu0O9BftDi+caB1UWDwJakNeWX1yYTvK+N1v4gRpKbj4AhctfYHMjq8qX9XTnXme5AGzCYC6HgYw2/RfalWzwNxI6k="\n`,
785
+ 'data: "dddd"\n',
492
786
  'id: msg_01MNsLe7n1uVLtu6W8rCFujD',
493
787
  'event: data',
494
- `data: {"type":"content_block_stop","index":0}\n`,
788
+ 'data: {"type":"thinking","thinking":null}\n',
495
789
  'id: msg_01MNsLe7n1uVLtu6W8rCFujD',
496
790
  'event: data',
497
- `data: ""\n`,
498
- 'id: msg_01MNsLe7n1uVLtu6W8rCFujD',
499
- 'event: text',
500
- `data: "9.8比9.11大。"\n`,
791
+ 'data: {"type":"content_block_start","index":0,"content_block":{"type":"abc","abc":""}}\n',
501
792
  'id: msg_01MNsLe7n1uVLtu6W8rCFujD',
502
793
  'event: data',
503
- `data: {"type":"content_block_stop","index":1}\n`,
504
- 'id: msg_01MNsLe7n1uVLtu6W8rCFujD',
505
- 'event: stop',
506
- 'data: "end_turn"\n',
507
- 'id: msg_01MNsLe7n1uVLtu6W8rCFujD',
508
- 'event: stop',
509
- 'data: "message_stop"\n',
794
+ 'data: {"type":"content_block_delta","index":0,"delta":{"type":"abc","abc":"123"}}\n',
510
795
  ].map((item) => `${item}\n`),
511
796
  );
512
797
  });
513
- it('should handle ReadableStream input', async () => {
514
- const mockReadableStream = new ReadableStream({
515
- start(controller) {
516
- controller.enqueue({
517
- type: 'message_start',
518
- message: { id: 'message_1', metadata: {} },
519
- });
520
- controller.enqueue({
521
- type: 'content_block_delta',
522
- delta: { type: 'text_delta', text: 'Hello' },
523
- });
524
- controller.enqueue({
525
- type: 'message_stop',
526
- });
527
- controller.close();
528
- },
529
- });
530
-
531
- const protocolStream = AnthropicStream(mockReadableStream);
532
-
533
- const decoder = new TextDecoder();
534
- const chunks = [];
535
-
536
- // @ts-ignore
537
- for await (const chunk of protocolStream) {
538
- chunks.push(decoder.decode(chunk, { stream: true }));
539
- }
540
-
541
- expect(chunks).toEqual([
542
- 'id: message_1\n',
543
- 'event: data\n',
544
- `data: {"id":"message_1","metadata":{}}\n\n`,
545
- 'id: message_1\n',
546
- 'event: text\n',
547
- `data: "Hello"\n\n`,
548
- 'id: message_1\n',
549
- 'event: stop\n',
550
- `data: "message_stop"\n\n`,
551
- ]);
552
- });
553
798
  });
@@ -15,7 +15,7 @@ import {
15
15
  export const transformAnthropicStream = (
16
16
  chunk: Anthropic.MessageStreamEvent,
17
17
  context: StreamContext,
18
- ): StreamProtocolChunk => {
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': {
@@ -23,48 +23,68 @@ export const transformAnthropicStream = (
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;
28
-
29
- // if toolIndex is not defined, set it to 0
30
- if (typeof context.toolIndex === 'undefined') {
31
- context.toolIndex = 0;
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
+ };
32
33
  }
33
- // if toolIndex is defined, increment it
34
- else {
35
- context.toolIndex += 1;
34
+
35
+ case 'text': {
36
+ return { data: chunk.content_block.text, id: context.id, type: 'data' };
36
37
  }
37
38
 
38
- const toolCall: StreamToolCallChunkData = {
39
- function: {
40
- arguments: '',
41
- name: toolChunk.name,
42
- },
43
- id: toolChunk.id,
44
- index: context.toolIndex,
45
- type: 'function',
46
- };
39
+ case 'tool_use': {
40
+ const toolChunk = chunk.content_block;
47
41
 
48
- context.tool = { id: toolChunk.id, index: context.toolIndex, name: toolChunk.name };
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
+ }
49
50
 
50
- return { data: [toolCall], id: context.id, type: 'tool_calls' };
51
- }
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
+ };
52
60
 
53
- if (chunk.content_block.type === 'thinking') {
54
- const thinkingChunk = chunk.content_block;
61
+ context.tool = { id: toolChunk.id, index: context.toolIndex, name: toolChunk.name };
55
62
 
56
- return { data: thinkingChunk.thinking, id: context.id, type: 'reasoning' };
57
- }
63
+ return { data: [toolCall], id: context.id, type: 'tool_calls' };
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
+ }
75
+
76
+ if (typeof thinkingChunk.thinking === 'string')
77
+ return { data: thinkingChunk.thinking, id: context.id, type: 'reasoning' };
78
+
79
+ return { data: thinkingChunk, id: context.id, type: 'data' };
80
+ }
58
81
 
59
- if (chunk.content_block.type === 'redacted_thinking') {
60
- return {
61
- data: chunk.content_block.data,
62
- id: context.id,
63
- type: 'reasoning',
64
- };
82
+ default: {
83
+ break;
84
+ }
65
85
  }
66
86
 
67
- return { data: chunk.content_block.text, id: context.id, type: 'data' };
87
+ return { data: chunk, id: context.id, type: 'data' };
68
88
  }
69
89
 
70
90
  case 'content_block_delta': {
@@ -93,7 +113,7 @@ export const transformAnthropicStream = (
93
113
  return {
94
114
  data: chunk.delta.signature,
95
115
  id: context.id,
96
- type: 'reasoning_signature' as any,
116
+ type: 'reasoning_signature',
97
117
  };
98
118
  }
99
119