@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.
- package/CHANGELOG.md +51 -0
- package/changelog/v1.json +18 -0
- package/docker-compose/local/docker-compose.yml +14 -0
- package/docker-compose/local/searxng-settings.yml +2582 -0
- package/docker-compose/setup.sh +3 -1
- package/docs/self-hosting/advanced/model-list.mdx +4 -2
- package/docs/self-hosting/advanced/model-list.zh-CN.mdx +4 -2
- package/package.json +7 -7
- package/src/app/(backend)/middleware/auth/index.ts +6 -0
- package/src/config/aiModels/google.ts +3 -3
- package/src/config/aiModels/groq.ts +10 -0
- package/src/config/aiModels/qwen.ts +43 -26
- package/src/const/message.ts +3 -0
- package/src/features/Conversation/Messages/Assistant/Tool/Render/CustomRender.tsx +7 -7
- package/src/features/MobileSwitchLoading/index.tsx +0 -1
- package/src/libs/agent-runtime/google/index.test.ts +8 -0
- package/src/libs/agent-runtime/google/index.ts +18 -5
- package/src/libs/agent-runtime/types/chat.ts +9 -1
- package/src/libs/agent-runtime/utils/anthropicHelpers.test.ts +113 -0
- package/src/libs/agent-runtime/utils/anthropicHelpers.ts +7 -4
- package/src/libs/agent-runtime/utils/streams/anthropic.test.ts +339 -94
- package/src/libs/agent-runtime/utils/streams/anthropic.ts +54 -34
- package/src/libs/agent-runtime/utils/streams/openai.test.ts +181 -0
- package/src/libs/agent-runtime/utils/streams/openai.ts +40 -30
- package/src/libs/agent-runtime/utils/streams/protocol.ts +4 -0
- package/src/services/__tests__/chat.test.ts +89 -50
- package/src/services/chat.ts +13 -1
- package/src/store/chat/slices/aiChat/actions/generateAIChat.ts +1 -1
- package/src/types/message/base.ts +1 -0
- package/src/utils/fetch/__tests__/fetchSSE.test.ts +113 -10
- package/src/utils/fetch/fetchSSE.ts +12 -3
- package/src/utils/parseModels.test.ts +14 -0
- package/src/utils/parseModels.ts +4 -0
@@ -384,7 +384,334 @@ describe('AnthropicStream', () => {
|
|
384
384
|
expect(onToolCallMock).toHaveBeenCalledTimes(6);
|
385
385
|
});
|
386
386
|
|
387
|
-
|
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: '
|
740
|
+
type: 'content_block_start',
|
419
741
|
index: 0,
|
420
|
-
|
742
|
+
content_block: { type: 'thinking', thinking: null },
|
421
743
|
},
|
422
744
|
{
|
423
|
-
type: '
|
745
|
+
type: 'content_block_start',
|
424
746
|
index: 0,
|
425
|
-
|
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
|
-
|
785
|
+
'data: "dddd"\n',
|
492
786
|
'id: msg_01MNsLe7n1uVLtu6W8rCFujD',
|
493
787
|
'event: data',
|
494
|
-
|
788
|
+
'data: {"type":"thinking","thinking":null}\n',
|
495
789
|
'id: msg_01MNsLe7n1uVLtu6W8rCFujD',
|
496
790
|
'event: data',
|
497
|
-
|
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
|
-
|
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
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
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
|
-
|
34
|
-
|
35
|
-
context.
|
34
|
+
|
35
|
+
case 'text': {
|
36
|
+
return { data: chunk.content_block.text, id: context.id, type: 'data' };
|
36
37
|
}
|
37
38
|
|
38
|
-
|
39
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
54
|
-
const thinkingChunk = chunk.content_block;
|
61
|
+
context.tool = { id: toolChunk.id, index: context.toolIndex, name: toolChunk.name };
|
55
62
|
|
56
|
-
|
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
|
-
|
60
|
-
|
61
|
-
|
62
|
-
id: context.id,
|
63
|
-
type: 'reasoning',
|
64
|
-
};
|
82
|
+
default: {
|
83
|
+
break;
|
84
|
+
}
|
65
85
|
}
|
66
86
|
|
67
|
-
return { data: chunk
|
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'
|
116
|
+
type: 'reasoning_signature',
|
97
117
|
};
|
98
118
|
}
|
99
119
|
|