@librechat/agents 3.2.64 → 3.2.65
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/dist/cjs/graphs/Graph.cjs +3 -0
- package/dist/cjs/graphs/Graph.cjs.map +1 -1
- package/dist/cjs/llm/anthropic/index.cjs +73 -9
- package/dist/cjs/llm/anthropic/index.cjs.map +1 -1
- package/dist/cjs/llm/anthropic/types.cjs.map +1 -1
- package/dist/cjs/llm/anthropic/utils/message_inputs.cjs +41 -9
- package/dist/cjs/llm/anthropic/utils/message_inputs.cjs.map +1 -1
- package/dist/cjs/llm/anthropic/utils/message_outputs.cjs +3 -1
- package/dist/cjs/llm/anthropic/utils/message_outputs.cjs.map +1 -1
- package/dist/cjs/llm/anthropic/utils/stream_events.cjs +337 -0
- package/dist/cjs/llm/anthropic/utils/stream_events.cjs.map +1 -0
- package/dist/cjs/tools/search/crw-scraper.cjs +165 -0
- package/dist/cjs/tools/search/crw-scraper.cjs.map +1 -0
- package/dist/cjs/tools/search/crw-search.cjs +105 -0
- package/dist/cjs/tools/search/crw-search.cjs.map +1 -0
- package/dist/cjs/tools/search/search.cjs +4 -2
- package/dist/cjs/tools/search/search.cjs.map +1 -1
- package/dist/cjs/tools/search/tool.cjs +15 -3
- package/dist/cjs/tools/search/tool.cjs.map +1 -1
- package/dist/esm/graphs/Graph.mjs +3 -0
- package/dist/esm/graphs/Graph.mjs.map +1 -1
- package/dist/esm/llm/anthropic/index.mjs +73 -9
- package/dist/esm/llm/anthropic/index.mjs.map +1 -1
- package/dist/esm/llm/anthropic/types.mjs.map +1 -1
- package/dist/esm/llm/anthropic/utils/message_inputs.mjs +41 -9
- package/dist/esm/llm/anthropic/utils/message_inputs.mjs.map +1 -1
- package/dist/esm/llm/anthropic/utils/message_outputs.mjs +3 -2
- package/dist/esm/llm/anthropic/utils/message_outputs.mjs.map +1 -1
- package/dist/esm/llm/anthropic/utils/stream_events.mjs +337 -0
- package/dist/esm/llm/anthropic/utils/stream_events.mjs.map +1 -0
- package/dist/esm/tools/search/crw-scraper.mjs +163 -0
- package/dist/esm/tools/search/crw-scraper.mjs.map +1 -0
- package/dist/esm/tools/search/crw-search.mjs +103 -0
- package/dist/esm/tools/search/crw-search.mjs.map +1 -0
- package/dist/esm/tools/search/search.mjs +4 -2
- package/dist/esm/tools/search/search.mjs.map +1 -1
- package/dist/esm/tools/search/tool.mjs +15 -3
- package/dist/esm/tools/search/tool.mjs.map +1 -1
- package/dist/types/llm/anthropic/index.d.ts +2 -0
- package/dist/types/llm/anthropic/types.d.ts +2 -0
- package/dist/types/llm/anthropic/utils/message_outputs.d.ts +1 -2
- package/dist/types/llm/anthropic/utils/stream_events.d.ts +25 -0
- package/dist/types/tools/search/crw-scraper.d.ts +41 -0
- package/dist/types/tools/search/crw-search.d.ts +4 -0
- package/dist/types/tools/search/types.d.ts +88 -3
- package/package.json +1 -1
- package/src/graphs/Graph.ts +15 -0
- package/src/llm/anthropic/index.ts +134 -10
- package/src/llm/anthropic/inherited-content-utils.spec.ts +10 -5
- package/src/llm/anthropic/inherited-stream-events.spec.ts +513 -9
- package/src/llm/anthropic/llm.spec.ts +100 -16
- package/src/llm/anthropic/types.ts +3 -0
- package/src/llm/anthropic/utils/message_inputs.ts +60 -3
- package/src/llm/anthropic/utils/message_outputs.ts +10 -2
- package/src/llm/anthropic/utils/stream_events.ts +471 -0
- package/src/tools/search/crw-scraper.ts +244 -0
- package/src/tools/search/crw-search.ts +167 -0
- package/src/tools/search/crw.test.ts +836 -0
- package/src/tools/search/search.ts +7 -1
- package/src/tools/search/tool.ts +23 -3
- package/src/tools/search/types.ts +103 -3
|
@@ -7,14 +7,11 @@
|
|
|
7
7
|
//
|
|
8
8
|
// Applicability to our fork:
|
|
9
9
|
// Our `CustomAnthropic` (imported here as `ChatAnthropic` from `@/llm/anthropic`)
|
|
10
|
-
// extends upstream `ChatAnthropicMessages` (1.5.1). It overrides the
|
|
11
|
-
// `_streamResponseChunks` path
|
|
12
|
-
//
|
|
13
|
-
//
|
|
14
|
-
//
|
|
15
|
-
// override only strips unsupported assistant prefill before delegating to super, so
|
|
16
|
-
// mocking it (as upstream's `MockStreamChatAnthropic` does) drives the real native
|
|
17
|
-
// conversion path. A probe confirmed the lifecycle/sub-streams resolve correctly.
|
|
10
|
+
// extends upstream `ChatAnthropicMessages` (1.5.1). It overrides both the legacy
|
|
11
|
+
// `_streamResponseChunks` path and `_streamChatModelEvents`, whose request setup
|
|
12
|
+
// mirrors upstream while using the local cumulative-usage converter. Mocking
|
|
13
|
+
// `createStreamWithRetry` (as upstream's `MockStreamChatAnthropic` does) drives
|
|
14
|
+
// the real native conversion path without making API calls.
|
|
18
15
|
//
|
|
19
16
|
// Adaptation:
|
|
20
17
|
// - vitest -> jest (`@jest/globals`; `vi.*` -> `jest.*`).
|
|
@@ -38,6 +35,7 @@ import type {
|
|
|
38
35
|
AnthropicStreamingMessageCreateParams,
|
|
39
36
|
} from './types';
|
|
40
37
|
import type { CustomAnthropicCallOptions } from './index';
|
|
38
|
+
import { _convertMessagesToAnthropicPayload } from './utils/message_inputs';
|
|
41
39
|
import { CustomAnthropic as ChatAnthropic } from './index';
|
|
42
40
|
|
|
43
41
|
// ─── Mock model ─────────────────────────────────────────────────
|
|
@@ -272,7 +270,7 @@ function cacheUsageEvents(): unknown[] {
|
|
|
272
270
|
stop_sequence: null,
|
|
273
271
|
usage: {
|
|
274
272
|
input_tokens: 100,
|
|
275
|
-
output_tokens:
|
|
273
|
+
output_tokens: 1,
|
|
276
274
|
cache_creation_input_tokens: 500,
|
|
277
275
|
cache_read_input_tokens: 200,
|
|
278
276
|
},
|
|
@@ -298,6 +296,302 @@ function cacheUsageEvents(): unknown[] {
|
|
|
298
296
|
];
|
|
299
297
|
}
|
|
300
298
|
|
|
299
|
+
function lateUsageEvents(): unknown[] {
|
|
300
|
+
return [
|
|
301
|
+
{
|
|
302
|
+
type: 'message_start',
|
|
303
|
+
message: {
|
|
304
|
+
id: 'msg_05MNO',
|
|
305
|
+
type: 'message',
|
|
306
|
+
role: 'assistant',
|
|
307
|
+
content: [],
|
|
308
|
+
model: 'claude-sonnet-4-20250514',
|
|
309
|
+
stop_reason: null,
|
|
310
|
+
stop_sequence: null,
|
|
311
|
+
usage: {
|
|
312
|
+
input_tokens: 0,
|
|
313
|
+
output_tokens: 7,
|
|
314
|
+
cache_creation_input_tokens: 0,
|
|
315
|
+
cache_read_input_tokens: 0,
|
|
316
|
+
},
|
|
317
|
+
},
|
|
318
|
+
},
|
|
319
|
+
{
|
|
320
|
+
type: 'message_delta',
|
|
321
|
+
delta: { stop_reason: 'end_turn', stop_sequence: null },
|
|
322
|
+
usage: {
|
|
323
|
+
input_tokens: 100,
|
|
324
|
+
output_tokens: 20,
|
|
325
|
+
cache_creation_input_tokens: 500,
|
|
326
|
+
cache_read_input_tokens: 200,
|
|
327
|
+
},
|
|
328
|
+
},
|
|
329
|
+
{
|
|
330
|
+
type: 'message_delta',
|
|
331
|
+
delta: { stop_reason: 'end_turn', stop_sequence: null },
|
|
332
|
+
usage: {
|
|
333
|
+
input_tokens: 100,
|
|
334
|
+
output_tokens: 42,
|
|
335
|
+
cache_creation_input_tokens: 500,
|
|
336
|
+
cache_read_input_tokens: 200,
|
|
337
|
+
},
|
|
338
|
+
},
|
|
339
|
+
{ type: 'message_stop' },
|
|
340
|
+
];
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
function webSearchResultEvents(): unknown[] {
|
|
344
|
+
return [
|
|
345
|
+
{
|
|
346
|
+
type: 'message_start',
|
|
347
|
+
message: {
|
|
348
|
+
id: 'msg_06PQR',
|
|
349
|
+
type: 'message',
|
|
350
|
+
role: 'assistant',
|
|
351
|
+
content: [],
|
|
352
|
+
model: 'claude-sonnet-4-20250514',
|
|
353
|
+
stop_reason: null,
|
|
354
|
+
stop_sequence: null,
|
|
355
|
+
usage: { input_tokens: 25, output_tokens: 0 },
|
|
356
|
+
},
|
|
357
|
+
},
|
|
358
|
+
{
|
|
359
|
+
type: 'content_block_start',
|
|
360
|
+
index: 0,
|
|
361
|
+
content_block: {
|
|
362
|
+
type: 'web_search_tool_result',
|
|
363
|
+
tool_use_id: 'srvtoolu_01WEB',
|
|
364
|
+
content: [
|
|
365
|
+
{
|
|
366
|
+
type: 'web_search_result',
|
|
367
|
+
url: 'https://example.com',
|
|
368
|
+
title: 'Example',
|
|
369
|
+
encrypted_content: 'encrypted',
|
|
370
|
+
page_age: null,
|
|
371
|
+
},
|
|
372
|
+
],
|
|
373
|
+
caller: { type: 'direct' },
|
|
374
|
+
},
|
|
375
|
+
},
|
|
376
|
+
{ type: 'content_block_stop', index: 0 },
|
|
377
|
+
{
|
|
378
|
+
type: 'message_delta',
|
|
379
|
+
delta: { stop_reason: 'end_turn', stop_sequence: null },
|
|
380
|
+
usage: { output_tokens: 1 },
|
|
381
|
+
},
|
|
382
|
+
{ type: 'message_stop' },
|
|
383
|
+
];
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
function serverToolUseEvents(): unknown[] {
|
|
387
|
+
return [
|
|
388
|
+
{
|
|
389
|
+
type: 'message_start',
|
|
390
|
+
message: {
|
|
391
|
+
id: 'msg_07STU',
|
|
392
|
+
type: 'message',
|
|
393
|
+
role: 'assistant',
|
|
394
|
+
content: [],
|
|
395
|
+
model: 'claude-sonnet-4-20250514',
|
|
396
|
+
stop_reason: null,
|
|
397
|
+
stop_sequence: null,
|
|
398
|
+
usage: { input_tokens: 25, output_tokens: 0 },
|
|
399
|
+
},
|
|
400
|
+
},
|
|
401
|
+
{
|
|
402
|
+
type: 'content_block_start',
|
|
403
|
+
index: 0,
|
|
404
|
+
content_block: {
|
|
405
|
+
type: 'server_tool_use',
|
|
406
|
+
id: 'srvtoolu_01SERVER',
|
|
407
|
+
name: 'web_search',
|
|
408
|
+
input: {},
|
|
409
|
+
},
|
|
410
|
+
},
|
|
411
|
+
{
|
|
412
|
+
type: 'content_block_delta',
|
|
413
|
+
index: 0,
|
|
414
|
+
delta: {
|
|
415
|
+
type: 'input_json_delta',
|
|
416
|
+
partial_json: '{"query":"weather"}',
|
|
417
|
+
},
|
|
418
|
+
},
|
|
419
|
+
{ type: 'content_block_stop', index: 0 },
|
|
420
|
+
{
|
|
421
|
+
type: 'message_delta',
|
|
422
|
+
delta: { stop_reason: 'end_turn', stop_sequence: null },
|
|
423
|
+
usage: { output_tokens: 8 },
|
|
424
|
+
},
|
|
425
|
+
{ type: 'message_stop' },
|
|
426
|
+
];
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
function citationEvents(): unknown[] {
|
|
430
|
+
return [
|
|
431
|
+
{
|
|
432
|
+
type: 'message_start',
|
|
433
|
+
message: {
|
|
434
|
+
id: 'msg_08VWX',
|
|
435
|
+
type: 'message',
|
|
436
|
+
role: 'assistant',
|
|
437
|
+
content: [],
|
|
438
|
+
model: 'claude-sonnet-4-20250514',
|
|
439
|
+
stop_reason: null,
|
|
440
|
+
stop_sequence: null,
|
|
441
|
+
usage: { input_tokens: 25, output_tokens: 0 },
|
|
442
|
+
},
|
|
443
|
+
},
|
|
444
|
+
{
|
|
445
|
+
type: 'content_block_start',
|
|
446
|
+
index: 0,
|
|
447
|
+
content_block: { type: 'text', text: '', citations: [] },
|
|
448
|
+
},
|
|
449
|
+
{
|
|
450
|
+
type: 'content_block_delta',
|
|
451
|
+
index: 0,
|
|
452
|
+
delta: {
|
|
453
|
+
type: 'citations_delta',
|
|
454
|
+
citation: {
|
|
455
|
+
type: 'web_search_result_location',
|
|
456
|
+
cited_text: 'Example source',
|
|
457
|
+
encrypted_index: 'encrypted-index',
|
|
458
|
+
title: 'Example',
|
|
459
|
+
url: 'https://example.com',
|
|
460
|
+
},
|
|
461
|
+
},
|
|
462
|
+
},
|
|
463
|
+
{
|
|
464
|
+
type: 'content_block_delta',
|
|
465
|
+
index: 0,
|
|
466
|
+
delta: { type: 'text_delta', text: 'Cited answer.' },
|
|
467
|
+
},
|
|
468
|
+
{ type: 'content_block_stop', index: 0 },
|
|
469
|
+
{
|
|
470
|
+
type: 'message_delta',
|
|
471
|
+
delta: { stop_reason: 'end_turn', stop_sequence: null },
|
|
472
|
+
usage: { output_tokens: 5 },
|
|
473
|
+
},
|
|
474
|
+
{ type: 'message_stop' },
|
|
475
|
+
];
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
function compactionEvents(): unknown[] {
|
|
479
|
+
return [
|
|
480
|
+
{
|
|
481
|
+
type: 'message_start',
|
|
482
|
+
message: {
|
|
483
|
+
id: 'msg_09YZA',
|
|
484
|
+
type: 'message',
|
|
485
|
+
role: 'assistant',
|
|
486
|
+
content: [],
|
|
487
|
+
model: 'claude-sonnet-4-20250514',
|
|
488
|
+
stop_reason: null,
|
|
489
|
+
stop_sequence: null,
|
|
490
|
+
usage: { input_tokens: 25, output_tokens: 0 },
|
|
491
|
+
},
|
|
492
|
+
},
|
|
493
|
+
{
|
|
494
|
+
type: 'content_block_start',
|
|
495
|
+
index: 0,
|
|
496
|
+
content_block: {
|
|
497
|
+
type: 'compaction',
|
|
498
|
+
content: null,
|
|
499
|
+
encrypted_content: null,
|
|
500
|
+
},
|
|
501
|
+
},
|
|
502
|
+
{
|
|
503
|
+
type: 'content_block_delta',
|
|
504
|
+
index: 0,
|
|
505
|
+
delta: {
|
|
506
|
+
type: 'compaction_delta',
|
|
507
|
+
content: 'Summary of the earlier conversation.',
|
|
508
|
+
encrypted_content: 'encrypted-summary',
|
|
509
|
+
},
|
|
510
|
+
},
|
|
511
|
+
{ type: 'content_block_stop', index: 0 },
|
|
512
|
+
{
|
|
513
|
+
type: 'message_delta',
|
|
514
|
+
delta: { stop_reason: 'end_turn', stop_sequence: null },
|
|
515
|
+
usage: { output_tokens: 5 },
|
|
516
|
+
},
|
|
517
|
+
{ type: 'message_stop' },
|
|
518
|
+
];
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
function redactedThinkingEvents(): unknown[] {
|
|
522
|
+
return [
|
|
523
|
+
{
|
|
524
|
+
type: 'message_start',
|
|
525
|
+
message: {
|
|
526
|
+
id: 'msg_10BCD',
|
|
527
|
+
type: 'message',
|
|
528
|
+
role: 'assistant',
|
|
529
|
+
content: [],
|
|
530
|
+
model: 'claude-sonnet-4-20250514',
|
|
531
|
+
stop_reason: null,
|
|
532
|
+
stop_sequence: null,
|
|
533
|
+
usage: { input_tokens: 25, output_tokens: 0 },
|
|
534
|
+
},
|
|
535
|
+
},
|
|
536
|
+
{
|
|
537
|
+
type: 'content_block_start',
|
|
538
|
+
index: 0,
|
|
539
|
+
content_block: {
|
|
540
|
+
type: 'redacted_thinking',
|
|
541
|
+
data: 'encrypted-thinking',
|
|
542
|
+
},
|
|
543
|
+
},
|
|
544
|
+
{ type: 'content_block_stop', index: 0 },
|
|
545
|
+
{
|
|
546
|
+
type: 'message_delta',
|
|
547
|
+
delta: { stop_reason: 'end_turn', stop_sequence: null },
|
|
548
|
+
usage: { output_tokens: 5 },
|
|
549
|
+
},
|
|
550
|
+
{ type: 'message_stop' },
|
|
551
|
+
];
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
function contextWindowExceededEvents(): unknown[] {
|
|
555
|
+
return [
|
|
556
|
+
...textOnlyEvents().slice(0, -2),
|
|
557
|
+
{
|
|
558
|
+
type: 'message_delta',
|
|
559
|
+
delta: {
|
|
560
|
+
stop_reason: 'model_context_window_exceeded',
|
|
561
|
+
stop_sequence: null,
|
|
562
|
+
},
|
|
563
|
+
usage: { output_tokens: 2 },
|
|
564
|
+
},
|
|
565
|
+
{ type: 'message_stop' },
|
|
566
|
+
];
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
function refusalEvents(): unknown[] {
|
|
570
|
+
return [
|
|
571
|
+
...textOnlyEvents().slice(0, -2),
|
|
572
|
+
{
|
|
573
|
+
type: 'message_delta',
|
|
574
|
+
delta: { stop_reason: 'refusal', stop_sequence: null },
|
|
575
|
+
usage: { output_tokens: 2 },
|
|
576
|
+
},
|
|
577
|
+
{ type: 'message_stop' },
|
|
578
|
+
];
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
function streamErrorEvents(): unknown[] {
|
|
582
|
+
return [
|
|
583
|
+
textOnlyEvents()[0],
|
|
584
|
+
{
|
|
585
|
+
type: 'error',
|
|
586
|
+
error: {
|
|
587
|
+
type: 'overloaded_error',
|
|
588
|
+
message: 'Anthropic stream overloaded',
|
|
589
|
+
},
|
|
590
|
+
request_id: 'req_01ERROR',
|
|
591
|
+
},
|
|
592
|
+
];
|
|
593
|
+
}
|
|
594
|
+
|
|
301
595
|
// ─── Tests ───────────────────────────────────────────────────────
|
|
302
596
|
|
|
303
597
|
describe('CustomAnthropic._streamChatModelEvents (inherited native)', () => {
|
|
@@ -572,6 +866,39 @@ describe('CustomAnthropic._streamChatModelEvents (inherited native)', () => {
|
|
|
572
866
|
expect(finish.usage.output_tokens).toBe(3);
|
|
573
867
|
});
|
|
574
868
|
|
|
869
|
+
test('usage events preserve cumulative message_delta totals', async () => {
|
|
870
|
+
const model = new MockStreamChatAnthropic(lateUsageEvents());
|
|
871
|
+
const events = await collectEvents(model, {
|
|
872
|
+
streamUsage: true,
|
|
873
|
+
} as BaseChatModelCallOptions);
|
|
874
|
+
|
|
875
|
+
const usageEvents = events.filter((event) => event.event === 'usage');
|
|
876
|
+
const lastUsage = usageEvents[usageEvents.length - 1] as {
|
|
877
|
+
usage: {
|
|
878
|
+
input_tokens: number;
|
|
879
|
+
output_tokens: number;
|
|
880
|
+
total_tokens: number;
|
|
881
|
+
input_token_details: {
|
|
882
|
+
cache_creation: number;
|
|
883
|
+
cache_read: number;
|
|
884
|
+
};
|
|
885
|
+
};
|
|
886
|
+
};
|
|
887
|
+
const expectedUsage = {
|
|
888
|
+
input_tokens: 800,
|
|
889
|
+
output_tokens: 42,
|
|
890
|
+
total_tokens: 842,
|
|
891
|
+
input_token_details: {
|
|
892
|
+
cache_creation: 500,
|
|
893
|
+
cache_read: 200,
|
|
894
|
+
},
|
|
895
|
+
};
|
|
896
|
+
expect(lastUsage.usage).toEqual(expectedUsage);
|
|
897
|
+
expect(
|
|
898
|
+
events.find((event) => event.event === 'message-finish')
|
|
899
|
+
).toMatchObject({ usage: expectedUsage });
|
|
900
|
+
});
|
|
901
|
+
|
|
575
902
|
test('no usage events when streamUsage is false', async () => {
|
|
576
903
|
const model = new MockStreamChatAnthropic(textOnlyEvents());
|
|
577
904
|
model.streamUsage = false;
|
|
@@ -651,6 +978,9 @@ describe('CustomAnthropic._streamChatModelEvents (inherited native)', () => {
|
|
|
651
978
|
|
|
652
979
|
expect(message.id).toBe('msg_03GHI');
|
|
653
980
|
expect(message._getType()).toBe('ai');
|
|
981
|
+
expect(message.response_metadata).toMatchObject({
|
|
982
|
+
model_provider: 'anthropic',
|
|
983
|
+
});
|
|
654
984
|
|
|
655
985
|
const content = message.content as Array<{
|
|
656
986
|
type: string;
|
|
@@ -670,6 +1000,153 @@ describe('CustomAnthropic._streamChatModelEvents (inherited native)', () => {
|
|
|
670
1000
|
expect(message.tool_calls?.[0]?.name).toBe('web_search');
|
|
671
1001
|
});
|
|
672
1002
|
|
|
1003
|
+
test('output preserves client tool calls for follow-up turns', async () => {
|
|
1004
|
+
const model = new MockStreamChatAnthropic(toolCallEvents());
|
|
1005
|
+
const stream = new ChatModelStream(streamEvents(model));
|
|
1006
|
+
const message = await stream.output;
|
|
1007
|
+
|
|
1008
|
+
const payload = _convertMessagesToAnthropicPayload([message]);
|
|
1009
|
+
expect(payload.messages[0]?.content).toEqual([
|
|
1010
|
+
{ type: 'text', text: 'Let me search.' },
|
|
1011
|
+
{
|
|
1012
|
+
type: 'tool_use',
|
|
1013
|
+
id: 'toolu_01ABC',
|
|
1014
|
+
name: 'web_search',
|
|
1015
|
+
input: { query: 'weather' },
|
|
1016
|
+
},
|
|
1017
|
+
]);
|
|
1018
|
+
});
|
|
1019
|
+
|
|
1020
|
+
test('output preserves signed thinking for follow-up turns', async () => {
|
|
1021
|
+
const model = new MockStreamChatAnthropic(thinkingPlusTextEvents());
|
|
1022
|
+
const stream = new ChatModelStream(streamEvents(model));
|
|
1023
|
+
const message = await stream.output;
|
|
1024
|
+
|
|
1025
|
+
const payload = _convertMessagesToAnthropicPayload([message]);
|
|
1026
|
+
expect(payload.messages[0]?.content).toEqual([
|
|
1027
|
+
{
|
|
1028
|
+
type: 'thinking',
|
|
1029
|
+
thinking: 'Let me reason...',
|
|
1030
|
+
signature: 'sig_abc',
|
|
1031
|
+
},
|
|
1032
|
+
{ type: 'text', text: 'The answer is 42.' },
|
|
1033
|
+
]);
|
|
1034
|
+
});
|
|
1035
|
+
|
|
1036
|
+
test('output preserves citations for follow-up turns', async () => {
|
|
1037
|
+
const model = new MockStreamChatAnthropic(citationEvents());
|
|
1038
|
+
const stream = new ChatModelStream(streamEvents(model));
|
|
1039
|
+
const message = await stream.output;
|
|
1040
|
+
const citation = {
|
|
1041
|
+
type: 'web_search_result_location',
|
|
1042
|
+
cited_text: 'Example source',
|
|
1043
|
+
encrypted_index: 'encrypted-index',
|
|
1044
|
+
title: 'Example',
|
|
1045
|
+
url: 'https://example.com',
|
|
1046
|
+
};
|
|
1047
|
+
|
|
1048
|
+
expect(message.content).toEqual([
|
|
1049
|
+
{ type: 'text', text: 'Cited answer.', citations: [citation] },
|
|
1050
|
+
]);
|
|
1051
|
+
const payload = _convertMessagesToAnthropicPayload([message]);
|
|
1052
|
+
expect(payload.messages[0]?.content).toEqual([
|
|
1053
|
+
{ type: 'text', text: 'Cited answer.', citations: [citation] },
|
|
1054
|
+
]);
|
|
1055
|
+
});
|
|
1056
|
+
|
|
1057
|
+
test('output preserves compaction blocks for follow-up turns', async () => {
|
|
1058
|
+
const model = new MockStreamChatAnthropic(compactionEvents());
|
|
1059
|
+
const stream = new ChatModelStream(streamEvents(model));
|
|
1060
|
+
const message = await stream.output;
|
|
1061
|
+
|
|
1062
|
+
expect(message.content).toEqual([
|
|
1063
|
+
{
|
|
1064
|
+
type: 'compaction',
|
|
1065
|
+
content: 'Summary of the earlier conversation.',
|
|
1066
|
+
encrypted_content: 'encrypted-summary',
|
|
1067
|
+
},
|
|
1068
|
+
]);
|
|
1069
|
+
const payload = _convertMessagesToAnthropicPayload([message]);
|
|
1070
|
+
expect(payload.messages[0]?.content).toEqual(message.content);
|
|
1071
|
+
});
|
|
1072
|
+
|
|
1073
|
+
test('output preserves redacted thinking for follow-up turns', async () => {
|
|
1074
|
+
const model = new MockStreamChatAnthropic(redactedThinkingEvents());
|
|
1075
|
+
const stream = new ChatModelStream(streamEvents(model));
|
|
1076
|
+
const message = await stream.output;
|
|
1077
|
+
|
|
1078
|
+
expect(message.content).toEqual([
|
|
1079
|
+
{ type: 'redacted_thinking', data: 'encrypted-thinking' },
|
|
1080
|
+
]);
|
|
1081
|
+
const payload = _convertMessagesToAnthropicPayload([message]);
|
|
1082
|
+
expect(payload.messages[0]?.content).toEqual(message.content);
|
|
1083
|
+
});
|
|
1084
|
+
|
|
1085
|
+
test('output preserves web search results for follow-up turns', async () => {
|
|
1086
|
+
const model = new MockStreamChatAnthropic(webSearchResultEvents());
|
|
1087
|
+
const stream = new ChatModelStream(streamEvents(model));
|
|
1088
|
+
const message = await stream.output;
|
|
1089
|
+
|
|
1090
|
+
expect(message.content).toEqual([
|
|
1091
|
+
{
|
|
1092
|
+
type: 'web_search_tool_result',
|
|
1093
|
+
tool_use_id: 'srvtoolu_01WEB',
|
|
1094
|
+
content: [
|
|
1095
|
+
{
|
|
1096
|
+
type: 'web_search_result',
|
|
1097
|
+
url: 'https://example.com',
|
|
1098
|
+
title: 'Example',
|
|
1099
|
+
encrypted_content: 'encrypted',
|
|
1100
|
+
page_age: null,
|
|
1101
|
+
},
|
|
1102
|
+
],
|
|
1103
|
+
caller: { type: 'direct' },
|
|
1104
|
+
},
|
|
1105
|
+
]);
|
|
1106
|
+
|
|
1107
|
+
const payload = _convertMessagesToAnthropicPayload([message]);
|
|
1108
|
+
expect(payload.messages[0]?.content).toEqual([
|
|
1109
|
+
{
|
|
1110
|
+
type: 'web_search_tool_result',
|
|
1111
|
+
tool_use_id: 'srvtoolu_01WEB',
|
|
1112
|
+
content: [
|
|
1113
|
+
{
|
|
1114
|
+
type: 'web_search_result',
|
|
1115
|
+
url: 'https://example.com',
|
|
1116
|
+
title: 'Example',
|
|
1117
|
+
encrypted_content: 'encrypted',
|
|
1118
|
+
page_age: null,
|
|
1119
|
+
},
|
|
1120
|
+
],
|
|
1121
|
+
},
|
|
1122
|
+
]);
|
|
1123
|
+
});
|
|
1124
|
+
|
|
1125
|
+
test('output preserves server tool arguments for follow-up turns', async () => {
|
|
1126
|
+
const model = new MockStreamChatAnthropic(serverToolUseEvents());
|
|
1127
|
+
const stream = new ChatModelStream(streamEvents(model));
|
|
1128
|
+
const message = await stream.output;
|
|
1129
|
+
|
|
1130
|
+
expect(message.content).toEqual([
|
|
1131
|
+
{
|
|
1132
|
+
type: 'server_tool_call',
|
|
1133
|
+
id: 'srvtoolu_01SERVER',
|
|
1134
|
+
name: 'web_search',
|
|
1135
|
+
args: { query: 'weather' },
|
|
1136
|
+
},
|
|
1137
|
+
]);
|
|
1138
|
+
|
|
1139
|
+
const payload = _convertMessagesToAnthropicPayload([message]);
|
|
1140
|
+
expect(payload.messages[0]?.content).toEqual([
|
|
1141
|
+
{
|
|
1142
|
+
type: 'server_tool_use',
|
|
1143
|
+
id: 'srvtoolu_01SERVER',
|
|
1144
|
+
name: 'web_search',
|
|
1145
|
+
input: { query: 'weather' },
|
|
1146
|
+
},
|
|
1147
|
+
]);
|
|
1148
|
+
});
|
|
1149
|
+
|
|
673
1150
|
test('usage sub-stream works end-to-end', async () => {
|
|
674
1151
|
const model = new MockStreamChatAnthropic(cacheUsageEvents());
|
|
675
1152
|
const stream = new ChatModelStream(
|
|
@@ -718,6 +1195,33 @@ describe('CustomAnthropic._streamChatModelEvents (inherited native)', () => {
|
|
|
718
1195
|
});
|
|
719
1196
|
});
|
|
720
1197
|
|
|
1198
|
+
describe('failure and finish semantics', () => {
|
|
1199
|
+
test('throws Anthropic errors emitted after the stream starts', async () => {
|
|
1200
|
+
const model = new MockStreamChatAnthropic(streamErrorEvents());
|
|
1201
|
+
await expect(collectEvents(model)).rejects.toThrow(
|
|
1202
|
+
'Anthropic stream overloaded'
|
|
1203
|
+
);
|
|
1204
|
+
});
|
|
1205
|
+
|
|
1206
|
+
test('maps model context exhaustion to a length finish reason', async () => {
|
|
1207
|
+
const model = new MockStreamChatAnthropic(contextWindowExceededEvents());
|
|
1208
|
+
const events = await collectEvents(model);
|
|
1209
|
+
|
|
1210
|
+
expect(events.find((event) => event.event === 'message-finish')).toEqual(
|
|
1211
|
+
expect.objectContaining({ reason: 'length' })
|
|
1212
|
+
);
|
|
1213
|
+
});
|
|
1214
|
+
|
|
1215
|
+
test('maps Anthropic refusals to a content-filter finish reason', async () => {
|
|
1216
|
+
const model = new MockStreamChatAnthropic(refusalEvents());
|
|
1217
|
+
const events = await collectEvents(model);
|
|
1218
|
+
|
|
1219
|
+
expect(events.find((event) => event.event === 'message-finish')).toEqual(
|
|
1220
|
+
expect.objectContaining({ reason: 'content_filter' })
|
|
1221
|
+
);
|
|
1222
|
+
});
|
|
1223
|
+
});
|
|
1224
|
+
|
|
721
1225
|
// Re-expressed from upstream's `streaming events` describe, which used vitest
|
|
722
1226
|
// custom matchers (`toHaveStreamText` / `toHaveStreamReasoning` /
|
|
723
1227
|
// `toHaveStreamToolCalls`) that do not exist in jest. The matchers wrap the
|