@lobehub/chat 1.65.0 → 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.
@@ -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