assistant-stream 0.3.13 → 0.3.15
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/README.md +39 -0
- package/dist/core/AssistantStream.d.ts +37 -0
- package/dist/core/AssistantStream.d.ts.map +1 -1
- package/dist/core/AssistantStream.js +22 -0
- package/dist/core/AssistantStream.js.map +1 -1
- package/dist/core/AssistantStreamChunk.d.ts +32 -0
- package/dist/core/AssistantStreamChunk.d.ts.map +1 -1
- package/dist/core/accumulators/assistant-message-accumulator.d.ts.map +1 -1
- package/dist/core/accumulators/assistant-message-accumulator.js +3 -0
- package/dist/core/accumulators/assistant-message-accumulator.js.map +1 -1
- package/dist/core/modules/assistant-stream.d.ts +68 -0
- package/dist/core/modules/assistant-stream.d.ts.map +1 -1
- package/dist/core/modules/assistant-stream.js +23 -0
- package/dist/core/modules/assistant-stream.js.map +1 -1
- package/dist/core/modules/tool-call.d.ts.map +1 -1
- package/dist/core/modules/tool-call.js +3 -0
- package/dist/core/modules/tool-call.js.map +1 -1
- package/dist/core/tool/ToolExecutionStream.d.ts.map +1 -1
- package/dist/core/tool/ToolExecutionStream.js +3 -0
- package/dist/core/tool/ToolExecutionStream.js.map +1 -1
- package/dist/core/tool/ToolResponse.d.ts +44 -0
- package/dist/core/tool/ToolResponse.d.ts.map +1 -1
- package/dist/core/tool/ToolResponse.js +27 -0
- package/dist/core/tool/ToolResponse.js.map +1 -1
- package/dist/core/tool/tool-types.d.ts +119 -2
- package/dist/core/tool/tool-types.d.ts.map +1 -1
- package/dist/core/tool/toolResultStream.d.ts +15 -0
- package/dist/core/tool/toolResultStream.d.ts.map +1 -1
- package/dist/core/tool/toolResultStream.js +39 -1
- package/dist/core/tool/toolResultStream.js.map +1 -1
- package/dist/core/utils/types.d.ts +4 -0
- package/dist/core/utils/types.d.ts.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/resumable/ResumableStreamContext.d.ts +27 -0
- package/dist/resumable/ResumableStreamContext.d.ts.map +1 -0
- package/dist/resumable/ResumableStreamContext.js +121 -0
- package/dist/resumable/ResumableStreamContext.js.map +1 -0
- package/dist/resumable/constants.d.ts +2 -0
- package/dist/resumable/constants.d.ts.map +1 -0
- package/dist/resumable/constants.js +2 -0
- package/dist/resumable/constants.js.map +1 -0
- package/dist/resumable/createResumableAssistantStreamResponse.d.ts +24 -0
- package/dist/resumable/createResumableAssistantStreamResponse.d.ts.map +1 -0
- package/dist/resumable/createResumableAssistantStreamResponse.js +40 -0
- package/dist/resumable/createResumableAssistantStreamResponse.js.map +1 -0
- package/dist/resumable/errors.d.ts +7 -0
- package/dist/resumable/errors.d.ts.map +1 -0
- package/dist/resumable/errors.js +15 -0
- package/dist/resumable/errors.js.map +1 -0
- package/dist/resumable/index.d.ts +7 -0
- package/dist/resumable/index.d.ts.map +1 -0
- package/dist/resumable/index.js +5 -0
- package/dist/resumable/index.js.map +1 -0
- package/dist/resumable/stores/InMemoryResumableStreamStore.d.ts +13 -0
- package/dist/resumable/stores/InMemoryResumableStreamStore.d.ts.map +1 -0
- package/dist/resumable/stores/InMemoryResumableStreamStore.js +199 -0
- package/dist/resumable/stores/InMemoryResumableStreamStore.js.map +1 -0
- package/dist/resumable/stores/ioredis.d.ts +10 -0
- package/dist/resumable/stores/ioredis.d.ts.map +1 -0
- package/dist/resumable/stores/ioredis.js +95 -0
- package/dist/resumable/stores/ioredis.js.map +1 -0
- package/dist/resumable/stores/redis-impl.d.ts +60 -0
- package/dist/resumable/stores/redis-impl.d.ts.map +1 -0
- package/dist/resumable/stores/redis-impl.js +198 -0
- package/dist/resumable/stores/redis-impl.js.map +1 -0
- package/dist/resumable/stores/redis.d.ts +39 -0
- package/dist/resumable/stores/redis.d.ts.map +1 -0
- package/dist/resumable/stores/redis.js +113 -0
- package/dist/resumable/stores/redis.js.map +1 -0
- package/dist/resumable/types.d.ts +30 -0
- package/dist/resumable/types.d.ts.map +1 -0
- package/dist/resumable/types.js +2 -0
- package/dist/resumable/types.js.map +1 -0
- package/package.json +28 -2
- package/src/core/AssistantStream.ts +37 -0
- package/src/core/AssistantStreamChunk.ts +32 -0
- package/src/core/accumulators/assistant-message-accumulator.ts +3 -0
- package/src/core/modules/assistant-stream.ts +68 -0
- package/src/core/modules/tool-call.ts +3 -0
- package/src/core/tool/ToolExecutionStream.ts +3 -0
- package/src/core/tool/ToolResponse.ts +50 -0
- package/src/core/tool/tool-types.ts +125 -2
- package/src/core/tool/toolResultStream.test.ts +360 -2
- package/src/core/tool/toolResultStream.ts +45 -1
- package/src/core/utils/types.ts +4 -0
- package/src/index.ts +5 -1
- package/src/resumable/ResumableStreamContext.test.ts +274 -0
- package/src/resumable/ResumableStreamContext.ts +187 -0
- package/src/resumable/__tests__/integration.test.ts +159 -0
- package/src/resumable/constants.ts +1 -0
- package/src/resumable/createResumableAssistantStreamResponse.test.ts +243 -0
- package/src/resumable/createResumableAssistantStreamResponse.ts +80 -0
- package/src/resumable/errors.ts +26 -0
- package/src/resumable/index.ts +36 -0
- package/src/resumable/stores/InMemoryResumableStreamStore.test.ts +285 -0
- package/src/resumable/stores/InMemoryResumableStreamStore.ts +237 -0
- package/src/resumable/stores/ioredis.ts +123 -0
- package/src/resumable/stores/redis-impl.ts +304 -0
- package/src/resumable/stores/redis.test.ts +265 -0
- package/src/resumable/stores/redis.ts +171 -0
- package/src/resumable/types.ts +49 -0
|
@@ -1,5 +1,10 @@
|
|
|
1
|
-
import { describe, expect, it } from "vitest";
|
|
2
|
-
import {
|
|
1
|
+
import { describe, expect, it, vi } from "vitest";
|
|
2
|
+
import {
|
|
3
|
+
toolResultStream as unstable_toolResultStream,
|
|
4
|
+
unstable_runPendingTools,
|
|
5
|
+
} from "./toolResultStream";
|
|
6
|
+
import { ToolResponse } from "./ToolResponse";
|
|
7
|
+
import type { AssistantStreamChunk } from "../AssistantStreamChunk";
|
|
3
8
|
import type { AssistantMessage, ToolCallPart } from "../utils/types";
|
|
4
9
|
import type { Tool } from "./tool-types";
|
|
5
10
|
|
|
@@ -397,4 +402,357 @@ describe("unstable_runPendingTools", () => {
|
|
|
397
402
|
});
|
|
398
403
|
});
|
|
399
404
|
});
|
|
405
|
+
|
|
406
|
+
describe("toModelOutput", () => {
|
|
407
|
+
it("attaches modelContent from toModelOutput onto the resolved tool-call part", async () => {
|
|
408
|
+
const tool: Tool = {
|
|
409
|
+
parameters: { type: "object", properties: {} },
|
|
410
|
+
execute: async () => ({
|
|
411
|
+
mediaType: "application/pdf",
|
|
412
|
+
base64: "JVBERi0xLjQK",
|
|
413
|
+
}),
|
|
414
|
+
toModelOutput: ({ output }) => {
|
|
415
|
+
const o = output as { base64: string; mediaType: string };
|
|
416
|
+
return [
|
|
417
|
+
{ type: "text", text: "PDF contents:" },
|
|
418
|
+
{
|
|
419
|
+
type: "file",
|
|
420
|
+
data: o.base64,
|
|
421
|
+
mediaType: o.mediaType,
|
|
422
|
+
},
|
|
423
|
+
];
|
|
424
|
+
},
|
|
425
|
+
};
|
|
426
|
+
|
|
427
|
+
const message: AssistantMessage = {
|
|
428
|
+
role: "assistant",
|
|
429
|
+
status: { type: "requires-action", reason: "tool-calls" },
|
|
430
|
+
parts: [
|
|
431
|
+
{
|
|
432
|
+
type: "tool-call",
|
|
433
|
+
toolCallId: "tc-1",
|
|
434
|
+
toolName: "readPdf",
|
|
435
|
+
args: {},
|
|
436
|
+
} as ToolCallPart,
|
|
437
|
+
],
|
|
438
|
+
content: [],
|
|
439
|
+
metadata: {
|
|
440
|
+
unstable_state: {},
|
|
441
|
+
unstable_data: [],
|
|
442
|
+
unstable_annotations: [],
|
|
443
|
+
steps: [],
|
|
444
|
+
custom: {},
|
|
445
|
+
},
|
|
446
|
+
};
|
|
447
|
+
|
|
448
|
+
const updated = await unstable_runPendingTools(
|
|
449
|
+
message,
|
|
450
|
+
{ readPdf: tool },
|
|
451
|
+
new AbortController().signal,
|
|
452
|
+
async () => {},
|
|
453
|
+
);
|
|
454
|
+
|
|
455
|
+
expect(updated.parts[0]).toMatchObject({
|
|
456
|
+
type: "tool-call",
|
|
457
|
+
state: "result",
|
|
458
|
+
result: { mediaType: "application/pdf", base64: "JVBERi0xLjQK" },
|
|
459
|
+
modelContent: [
|
|
460
|
+
{ type: "text", text: "PDF contents:" },
|
|
461
|
+
{
|
|
462
|
+
type: "file",
|
|
463
|
+
data: "JVBERi0xLjQK",
|
|
464
|
+
mediaType: "application/pdf",
|
|
465
|
+
},
|
|
466
|
+
],
|
|
467
|
+
});
|
|
468
|
+
});
|
|
469
|
+
|
|
470
|
+
it("does not call toModelOutput when the ToolResponse already carries modelContent", async () => {
|
|
471
|
+
let called = false;
|
|
472
|
+
const tool: Tool = {
|
|
473
|
+
parameters: { type: "object", properties: {} },
|
|
474
|
+
execute: async () =>
|
|
475
|
+
new ToolResponse({
|
|
476
|
+
result: { ok: true },
|
|
477
|
+
modelContent: [{ type: "text", text: "preset" }],
|
|
478
|
+
}),
|
|
479
|
+
toModelOutput: () => {
|
|
480
|
+
called = true;
|
|
481
|
+
return [{ type: "text", text: "should not run" }];
|
|
482
|
+
},
|
|
483
|
+
};
|
|
484
|
+
|
|
485
|
+
const message: AssistantMessage = {
|
|
486
|
+
role: "assistant",
|
|
487
|
+
status: { type: "requires-action", reason: "tool-calls" },
|
|
488
|
+
parts: [
|
|
489
|
+
{
|
|
490
|
+
type: "tool-call",
|
|
491
|
+
toolCallId: "tc-1",
|
|
492
|
+
toolName: "preset",
|
|
493
|
+
args: {},
|
|
494
|
+
} as ToolCallPart,
|
|
495
|
+
],
|
|
496
|
+
content: [],
|
|
497
|
+
metadata: {
|
|
498
|
+
unstable_state: {},
|
|
499
|
+
unstable_data: [],
|
|
500
|
+
unstable_annotations: [],
|
|
501
|
+
steps: [],
|
|
502
|
+
custom: {},
|
|
503
|
+
},
|
|
504
|
+
};
|
|
505
|
+
|
|
506
|
+
const updated = await unstable_runPendingTools(
|
|
507
|
+
message,
|
|
508
|
+
{ preset: tool },
|
|
509
|
+
new AbortController().signal,
|
|
510
|
+
async () => {},
|
|
511
|
+
);
|
|
512
|
+
|
|
513
|
+
expect(called).toBe(false);
|
|
514
|
+
expect(updated.parts[0]).toMatchObject({
|
|
515
|
+
type: "tool-call",
|
|
516
|
+
state: "result",
|
|
517
|
+
modelContent: [{ type: "text", text: "preset" }],
|
|
518
|
+
});
|
|
519
|
+
});
|
|
520
|
+
|
|
521
|
+
it("falls back to the successful execute result when toModelOutput itself throws", async () => {
|
|
522
|
+
const warn = vi.spyOn(console, "warn").mockImplementation(() => {});
|
|
523
|
+
const tool: Tool = {
|
|
524
|
+
parameters: { type: "object", properties: {} },
|
|
525
|
+
execute: async () => ({ ok: true }),
|
|
526
|
+
toModelOutput: () => {
|
|
527
|
+
throw new Error("projection failed");
|
|
528
|
+
},
|
|
529
|
+
};
|
|
530
|
+
|
|
531
|
+
const message: AssistantMessage = {
|
|
532
|
+
role: "assistant",
|
|
533
|
+
status: { type: "requires-action", reason: "tool-calls" },
|
|
534
|
+
parts: [
|
|
535
|
+
{
|
|
536
|
+
type: "tool-call",
|
|
537
|
+
toolCallId: "tc-1",
|
|
538
|
+
toolName: "flaky",
|
|
539
|
+
args: {},
|
|
540
|
+
} as ToolCallPart,
|
|
541
|
+
],
|
|
542
|
+
content: [],
|
|
543
|
+
metadata: {
|
|
544
|
+
unstable_state: {},
|
|
545
|
+
unstable_data: [],
|
|
546
|
+
unstable_annotations: [],
|
|
547
|
+
steps: [],
|
|
548
|
+
custom: {},
|
|
549
|
+
},
|
|
550
|
+
};
|
|
551
|
+
|
|
552
|
+
const updated = await unstable_runPendingTools(
|
|
553
|
+
message,
|
|
554
|
+
{ flaky: tool },
|
|
555
|
+
new AbortController().signal,
|
|
556
|
+
async () => {},
|
|
557
|
+
);
|
|
558
|
+
|
|
559
|
+
expect(updated.parts[0]).toMatchObject({
|
|
560
|
+
type: "tool-call",
|
|
561
|
+
state: "result",
|
|
562
|
+
result: { ok: true },
|
|
563
|
+
isError: false,
|
|
564
|
+
});
|
|
565
|
+
expect(updated.parts[0]).not.toHaveProperty("modelContent");
|
|
566
|
+
expect(warn).toHaveBeenCalledWith(
|
|
567
|
+
expect.stringContaining(`tool "flaky" toModelOutput threw`),
|
|
568
|
+
expect.any(Error),
|
|
569
|
+
);
|
|
570
|
+
warn.mockRestore();
|
|
571
|
+
});
|
|
572
|
+
|
|
573
|
+
it("forwards modelContent through the streaming path (toolResultStream + ToolExecutionStream)", async () => {
|
|
574
|
+
const tool: Tool = {
|
|
575
|
+
parameters: { type: "object", properties: {} },
|
|
576
|
+
execute: async () => ({
|
|
577
|
+
mediaType: "application/pdf",
|
|
578
|
+
base64: "JVBERi0xLjQK",
|
|
579
|
+
}),
|
|
580
|
+
toModelOutput: ({ output }) => {
|
|
581
|
+
const o = output as { mediaType: string; base64: string };
|
|
582
|
+
return [
|
|
583
|
+
{ type: "text", text: "PDF contents:" },
|
|
584
|
+
{ type: "file", data: o.base64, mediaType: o.mediaType },
|
|
585
|
+
];
|
|
586
|
+
},
|
|
587
|
+
};
|
|
588
|
+
|
|
589
|
+
const inputChunks: AssistantStreamChunk[] = [
|
|
590
|
+
{
|
|
591
|
+
type: "part-start",
|
|
592
|
+
path: [],
|
|
593
|
+
part: {
|
|
594
|
+
type: "tool-call",
|
|
595
|
+
toolCallId: "tc-stream-1",
|
|
596
|
+
toolName: "readPdf",
|
|
597
|
+
},
|
|
598
|
+
},
|
|
599
|
+
{ type: "text-delta", path: [0], textDelta: "{}" },
|
|
600
|
+
{ type: "tool-call-args-text-finish", path: [0] },
|
|
601
|
+
{ type: "part-finish", path: [0] },
|
|
602
|
+
];
|
|
603
|
+
|
|
604
|
+
const inputStream = new ReadableStream<AssistantStreamChunk>({
|
|
605
|
+
start(controller) {
|
|
606
|
+
for (const chunk of inputChunks) controller.enqueue(chunk);
|
|
607
|
+
controller.close();
|
|
608
|
+
},
|
|
609
|
+
});
|
|
610
|
+
|
|
611
|
+
const outputChunks: AssistantStreamChunk[] = [];
|
|
612
|
+
await inputStream
|
|
613
|
+
.pipeThrough(
|
|
614
|
+
unstable_toolResultStream(
|
|
615
|
+
{ readPdf: tool },
|
|
616
|
+
new AbortController().signal,
|
|
617
|
+
async () => {},
|
|
618
|
+
),
|
|
619
|
+
)
|
|
620
|
+
.pipeTo(
|
|
621
|
+
new WritableStream<AssistantStreamChunk>({
|
|
622
|
+
write(chunk) {
|
|
623
|
+
outputChunks.push(chunk);
|
|
624
|
+
},
|
|
625
|
+
}),
|
|
626
|
+
);
|
|
627
|
+
|
|
628
|
+
const resultChunk = outputChunks.find((c) => c.type === "result") as
|
|
629
|
+
| (AssistantStreamChunk & { type: "result" })
|
|
630
|
+
| undefined;
|
|
631
|
+
expect(resultChunk).toBeDefined();
|
|
632
|
+
expect(resultChunk?.result).toEqual({
|
|
633
|
+
mediaType: "application/pdf",
|
|
634
|
+
base64: "JVBERi0xLjQK",
|
|
635
|
+
});
|
|
636
|
+
expect(resultChunk?.isError).toBe(false);
|
|
637
|
+
expect(resultChunk?.modelContent).toEqual([
|
|
638
|
+
{ type: "text", text: "PDF contents:" },
|
|
639
|
+
{
|
|
640
|
+
type: "file",
|
|
641
|
+
data: "JVBERi0xLjQK",
|
|
642
|
+
mediaType: "application/pdf",
|
|
643
|
+
},
|
|
644
|
+
]);
|
|
645
|
+
});
|
|
646
|
+
|
|
647
|
+
it("falls back to the plain result when toModelOutput throws in the streaming path", async () => {
|
|
648
|
+
const warn = vi.spyOn(console, "warn").mockImplementation(() => {});
|
|
649
|
+
const tool: Tool = {
|
|
650
|
+
parameters: { type: "object", properties: {} },
|
|
651
|
+
execute: async () => ({ ok: true }),
|
|
652
|
+
toModelOutput: () => {
|
|
653
|
+
throw new Error("projection failed");
|
|
654
|
+
},
|
|
655
|
+
};
|
|
656
|
+
|
|
657
|
+
const inputChunks: AssistantStreamChunk[] = [
|
|
658
|
+
{
|
|
659
|
+
type: "part-start",
|
|
660
|
+
path: [],
|
|
661
|
+
part: {
|
|
662
|
+
type: "tool-call",
|
|
663
|
+
toolCallId: "tc-stream-err",
|
|
664
|
+
toolName: "flaky",
|
|
665
|
+
},
|
|
666
|
+
},
|
|
667
|
+
{ type: "text-delta", path: [0], textDelta: "{}" },
|
|
668
|
+
{ type: "tool-call-args-text-finish", path: [0] },
|
|
669
|
+
{ type: "part-finish", path: [0] },
|
|
670
|
+
];
|
|
671
|
+
|
|
672
|
+
const inputStream = new ReadableStream<AssistantStreamChunk>({
|
|
673
|
+
start(controller) {
|
|
674
|
+
for (const chunk of inputChunks) controller.enqueue(chunk);
|
|
675
|
+
controller.close();
|
|
676
|
+
},
|
|
677
|
+
});
|
|
678
|
+
|
|
679
|
+
const outputChunks: AssistantStreamChunk[] = [];
|
|
680
|
+
await inputStream
|
|
681
|
+
.pipeThrough(
|
|
682
|
+
unstable_toolResultStream(
|
|
683
|
+
{ flaky: tool },
|
|
684
|
+
new AbortController().signal,
|
|
685
|
+
async () => {},
|
|
686
|
+
),
|
|
687
|
+
)
|
|
688
|
+
.pipeTo(
|
|
689
|
+
new WritableStream<AssistantStreamChunk>({
|
|
690
|
+
write(chunk) {
|
|
691
|
+
outputChunks.push(chunk);
|
|
692
|
+
},
|
|
693
|
+
}),
|
|
694
|
+
);
|
|
695
|
+
|
|
696
|
+
const resultChunk = outputChunks.find((c) => c.type === "result") as
|
|
697
|
+
| (AssistantStreamChunk & { type: "result" })
|
|
698
|
+
| undefined;
|
|
699
|
+
expect(resultChunk).toBeDefined();
|
|
700
|
+
expect(resultChunk?.result).toEqual({ ok: true });
|
|
701
|
+
expect(resultChunk?.isError).toBe(false);
|
|
702
|
+
expect(resultChunk?.modelContent).toBeUndefined();
|
|
703
|
+
expect(warn).toHaveBeenCalledWith(
|
|
704
|
+
expect.stringContaining(`tool "flaky" toModelOutput threw`),
|
|
705
|
+
expect.any(Error),
|
|
706
|
+
);
|
|
707
|
+
warn.mockRestore();
|
|
708
|
+
});
|
|
709
|
+
|
|
710
|
+
it("does not call toModelOutput when the tool errors", async () => {
|
|
711
|
+
let called = false;
|
|
712
|
+
const tool: Tool = {
|
|
713
|
+
parameters: { type: "object", properties: {} },
|
|
714
|
+
execute: async () => {
|
|
715
|
+
throw new Error("boom");
|
|
716
|
+
},
|
|
717
|
+
toModelOutput: () => {
|
|
718
|
+
called = true;
|
|
719
|
+
return [{ type: "text", text: "should not run" }];
|
|
720
|
+
},
|
|
721
|
+
};
|
|
722
|
+
|
|
723
|
+
const message: AssistantMessage = {
|
|
724
|
+
role: "assistant",
|
|
725
|
+
status: { type: "requires-action", reason: "tool-calls" },
|
|
726
|
+
parts: [
|
|
727
|
+
{
|
|
728
|
+
type: "tool-call",
|
|
729
|
+
toolCallId: "tc-1",
|
|
730
|
+
toolName: "broken",
|
|
731
|
+
args: {},
|
|
732
|
+
} as ToolCallPart,
|
|
733
|
+
],
|
|
734
|
+
content: [],
|
|
735
|
+
metadata: {
|
|
736
|
+
unstable_state: {},
|
|
737
|
+
unstable_data: [],
|
|
738
|
+
unstable_annotations: [],
|
|
739
|
+
steps: [],
|
|
740
|
+
custom: {},
|
|
741
|
+
},
|
|
742
|
+
};
|
|
743
|
+
|
|
744
|
+
try {
|
|
745
|
+
await unstable_runPendingTools(
|
|
746
|
+
message,
|
|
747
|
+
{ broken: tool },
|
|
748
|
+
new AbortController().signal,
|
|
749
|
+
async () => {},
|
|
750
|
+
);
|
|
751
|
+
} catch {
|
|
752
|
+
// execute throws; toModelOutput must not have been consulted
|
|
753
|
+
}
|
|
754
|
+
|
|
755
|
+
expect(called).toBe(false);
|
|
756
|
+
});
|
|
757
|
+
});
|
|
400
758
|
});
|
|
@@ -87,7 +87,33 @@ function getToolResponse(
|
|
|
87
87
|
abortSignal,
|
|
88
88
|
human: (payload: unknown) => human(toolCall.toolCallId, payload),
|
|
89
89
|
})) as unknown as ReadonlyJSONValue;
|
|
90
|
-
|
|
90
|
+
const response = ToolResponse.toResponse(result);
|
|
91
|
+
if (
|
|
92
|
+
tool.toModelOutput &&
|
|
93
|
+
!response.isError &&
|
|
94
|
+
response.modelContent === undefined
|
|
95
|
+
) {
|
|
96
|
+
try {
|
|
97
|
+
const modelContent = await tool.toModelOutput({
|
|
98
|
+
toolCallId: toolCall.toolCallId,
|
|
99
|
+
input: toolCall.args,
|
|
100
|
+
output: response.result,
|
|
101
|
+
});
|
|
102
|
+
return new ToolResponse({
|
|
103
|
+
result: response.result,
|
|
104
|
+
artifact: response.artifact,
|
|
105
|
+
isError: response.isError,
|
|
106
|
+
messages: response.messages,
|
|
107
|
+
modelContent,
|
|
108
|
+
});
|
|
109
|
+
} catch (e) {
|
|
110
|
+
console.warn(
|
|
111
|
+
`[assistant-stream] tool "${toolCall.toolName}" toModelOutput threw; falling back to default projection.`,
|
|
112
|
+
e,
|
|
113
|
+
);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
return response;
|
|
91
117
|
})();
|
|
92
118
|
|
|
93
119
|
return Promise.race([executePromise, abortPromise]);
|
|
@@ -169,6 +195,9 @@ export async function unstable_runPendingTools(
|
|
|
169
195
|
...(toolResponse.artifact !== undefined
|
|
170
196
|
? { artifact: toolResponse.artifact }
|
|
171
197
|
: {}),
|
|
198
|
+
...(toolResponse.modelContent !== undefined
|
|
199
|
+
? { modelContent: toolResponse.modelContent }
|
|
200
|
+
: {}),
|
|
172
201
|
result: toolResponse.result as ReadonlyJSONValue,
|
|
173
202
|
isError: toolResponse.isError,
|
|
174
203
|
};
|
|
@@ -185,10 +214,25 @@ export async function unstable_runPendingTools(
|
|
|
185
214
|
}
|
|
186
215
|
|
|
187
216
|
export type ToolResultStreamOptions = {
|
|
217
|
+
/** Called immediately before a frontend tool's `execute` function runs. */
|
|
188
218
|
onExecutionStart?: (toolCallId: string, toolName: string) => void;
|
|
219
|
+
/** Called after frontend tool execution finishes or fails. */
|
|
189
220
|
onExecutionEnd?: (toolCallId: string, toolName: string) => void;
|
|
190
221
|
};
|
|
191
222
|
|
|
223
|
+
/**
|
|
224
|
+
* Transform stream that executes frontend tools and appends tool results.
|
|
225
|
+
*
|
|
226
|
+
* The transform watches streamed tool-call arguments, runs the matching
|
|
227
|
+
* frontend tool once its arguments are complete, and emits a result chunk for
|
|
228
|
+
* the tool call. Backend and human tools pass through according to their tool
|
|
229
|
+
* definition.
|
|
230
|
+
*
|
|
231
|
+
* @param tools Tool registry or function returning the current registry.
|
|
232
|
+
* @param abortSignal Signal, or signal getter, used for the current run.
|
|
233
|
+
* @param human Callback used to resolve human-tool requests from UI input.
|
|
234
|
+
* @param options Optional execution lifecycle callbacks.
|
|
235
|
+
*/
|
|
192
236
|
export function toolResultStream(
|
|
193
237
|
tools:
|
|
194
238
|
| Record<string, Tool>
|
package/src/core/utils/types.ts
CHANGED
|
@@ -2,6 +2,7 @@ import type {
|
|
|
2
2
|
ReadonlyJSONObject,
|
|
3
3
|
ReadonlyJSONValue,
|
|
4
4
|
} from "../../utils/json/json-value";
|
|
5
|
+
import type { ToolModelContentPart } from "../tool/tool-types";
|
|
5
6
|
|
|
6
7
|
type TextStatus =
|
|
7
8
|
| {
|
|
@@ -61,6 +62,7 @@ type ToolCallPartBase = {
|
|
|
61
62
|
args: ReadonlyJSONObject;
|
|
62
63
|
artifact?: ReadonlyJSONValue;
|
|
63
64
|
result?: ReadonlyJSONValue;
|
|
65
|
+
modelContent?: readonly ToolModelContentPart[];
|
|
64
66
|
isError?: boolean;
|
|
65
67
|
parentId?: string;
|
|
66
68
|
};
|
|
@@ -68,12 +70,14 @@ type ToolCallPartBase = {
|
|
|
68
70
|
type ToolCallPartWithoutResult = ToolCallPartBase & {
|
|
69
71
|
state: "partial-call" | "call";
|
|
70
72
|
result?: undefined;
|
|
73
|
+
modelContent?: undefined;
|
|
71
74
|
};
|
|
72
75
|
|
|
73
76
|
type ToolCallPartWithResult = ToolCallPartBase & {
|
|
74
77
|
state: "result";
|
|
75
78
|
result: ReadonlyJSONValue;
|
|
76
79
|
artifact?: ReadonlyJSONValue;
|
|
80
|
+
modelContent?: readonly ToolModelContentPart[];
|
|
77
81
|
isError?: boolean;
|
|
78
82
|
};
|
|
79
83
|
|
package/src/index.ts
CHANGED
|
@@ -35,7 +35,11 @@ export type {
|
|
|
35
35
|
DataPart,
|
|
36
36
|
} from "./core/utils/types";
|
|
37
37
|
|
|
38
|
-
export type {
|
|
38
|
+
export type {
|
|
39
|
+
Tool,
|
|
40
|
+
ToolModelContentPart,
|
|
41
|
+
ToolModelOutputFunction,
|
|
42
|
+
} from "./core/tool/tool-types";
|
|
39
43
|
export { ToolResponse, type ToolResponseLike } from "./core/tool/ToolResponse";
|
|
40
44
|
export { ToolExecutionStream } from "./core/tool/ToolExecutionStream";
|
|
41
45
|
export type { ToolCallReader } from "./core/tool/tool-types";
|