@assistant-ui/ai-sdk 0.0.6 → 0.0.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (107) hide show
  1. package/README.md +1 -1
  2. package/dist/adapters/aiSDKFormatAdapter.d.ts +2 -2
  3. package/dist/adapters/aiSDKFormatAdapter.d.ts.map +1 -0
  4. package/dist/adapters/vercelAttachmentAdapter.d.ts +2 -5
  5. package/dist/adapters/vercelAttachmentAdapter.d.ts.map +1 -1
  6. package/dist/adapters/vercelAttachmentAdapter.js +3 -3
  7. package/dist/adapters/vercelAttachmentAdapter.js.map +1 -1
  8. package/dist/aiSDKExtras.d.ts +6 -9
  9. package/dist/aiSDKExtras.d.ts.map +1 -1
  10. package/dist/converters/convertMessage.d.ts +34 -25
  11. package/dist/converters/convertMessage.d.ts.map +1 -1
  12. package/dist/converters/convertMessage.js +78 -8
  13. package/dist/converters/convertMessage.js.map +1 -1
  14. package/dist/converters/modelContentEnvelope.d.ts +10 -12
  15. package/dist/converters/modelContentEnvelope.d.ts.map +1 -1
  16. package/dist/converters/toCreateMessage.d.ts +3 -6
  17. package/dist/converters/toCreateMessage.d.ts.map +1 -1
  18. package/dist/converters/toolOutputConversion.d.ts +22 -25
  19. package/dist/converters/toolOutputConversion.d.ts.map +1 -1
  20. package/dist/hooks.d.ts +4 -7
  21. package/dist/hooks.d.ts.map +1 -1
  22. package/dist/index.d.ts +17 -14
  23. package/dist/index.d.ts.map +1 -0
  24. package/dist/index.native.d.ts +15 -12
  25. package/dist/index.native.d.ts.map +1 -0
  26. package/dist/model-context/injectInteractableContext.d.ts +3 -6
  27. package/dist/model-context/injectInteractableContext.d.ts.map +1 -1
  28. package/dist/model-context/injectQuoteContext.d.ts +2 -5
  29. package/dist/model-context/injectQuoteContext.d.ts.map +1 -1
  30. package/dist/runtime/AISDKChat.d.ts +4 -7
  31. package/dist/runtime/AISDKChat.d.ts.map +1 -1
  32. package/dist/runtime/AISDKChat.js +1 -1
  33. package/dist/runtime/AISDKThreads.d.ts +29 -32
  34. package/dist/runtime/AISDKThreads.d.ts.map +1 -1
  35. package/dist/runtime/AISDKThreads.js +26 -33
  36. package/dist/runtime/AISDKThreads.js.map +1 -1
  37. package/dist/runtime/sdkIdentity.d.ts +2 -5
  38. package/dist/runtime/sdkIdentity.d.ts.map +1 -1
  39. package/dist/runtime/sdkIdentity.js +1 -1
  40. package/dist/runtime/useAISDKRuntime.d.ts +72 -61
  41. package/dist/runtime/useAISDKRuntime.d.ts.map +1 -1
  42. package/dist/runtime/useAISDKRuntime.js +170 -56
  43. package/dist/runtime/useAISDKRuntime.js.map +1 -1
  44. package/dist/runtime/useChatRuntime.d.ts +8 -11
  45. package/dist/runtime/useChatRuntime.d.ts.map +1 -1
  46. package/dist/runtime/useChatRuntime.js +14 -10
  47. package/dist/runtime/useChatRuntime.js.map +1 -1
  48. package/dist/runtime/useChatThread.d.ts +100 -81
  49. package/dist/runtime/useChatThread.d.ts.map +1 -1
  50. package/dist/runtime/useChatThread.js +33 -13
  51. package/dist/runtime/useChatThread.js.map +1 -1
  52. package/dist/runtime/useExternalHistory.d.ts +8 -9
  53. package/dist/runtime/useExternalHistory.d.ts.map +1 -1
  54. package/dist/runtime/useExternalHistory.js +236 -58
  55. package/dist/runtime/useExternalHistory.js.map +1 -1
  56. package/dist/runtime/useResourceCleanup.d.ts +1 -4
  57. package/dist/runtime/useResourceCleanup.d.ts.map +1 -1
  58. package/dist/runtime/useResourceCleanup.js +38 -14
  59. package/dist/runtime/useResourceCleanup.js.map +1 -1
  60. package/dist/runtime/useStreamingTiming.d.ts +5 -8
  61. package/dist/runtime/useStreamingTiming.d.ts.map +1 -1
  62. package/dist/runtime/useStreamingTiming.js +1 -1
  63. package/dist/tools/frontendTools.d.ts +24 -27
  64. package/dist/tools/frontendTools.d.ts.map +1 -1
  65. package/dist/tools/generativeTools.d.ts +29 -32
  66. package/dist/tools/generativeTools.d.ts.map +1 -1
  67. package/dist/tools/mcp-stdio.node.d.ts +2 -2
  68. package/dist/tools/mcp-stdio.node.d.ts.map +1 -0
  69. package/dist/tools/mcp-stdio.unsupported.d.ts +2 -5
  70. package/dist/tools/mcp-stdio.unsupported.d.ts.map +1 -1
  71. package/dist/transport/AssistantChatTransport.d.ts +17 -20
  72. package/dist/transport/AssistantChatTransport.d.ts.map +1 -1
  73. package/dist/transport/resumable.d.ts +29 -32
  74. package/dist/transport/resumable.d.ts.map +1 -1
  75. package/dist/usage.d.ts +12 -15
  76. package/dist/usage.d.ts.map +1 -1
  77. package/dist/usage.js.map +1 -1
  78. package/dist/utils/getVercelAIMessages.d.ts +3 -6
  79. package/dist/utils/getVercelAIMessages.d.ts.map +1 -1
  80. package/dist/utils/sliceMessagesUntil.d.ts +2 -5
  81. package/dist/utils/sliceMessagesUntil.d.ts.map +1 -1
  82. package/package.json +16 -14
  83. package/src/adapters/vercelAttachmentAdapter.test.ts +23 -0
  84. package/src/adapters/vercelAttachmentAdapter.ts +2 -2
  85. package/src/converters/convertMessage.test.ts +616 -3
  86. package/src/converters/convertMessage.ts +187 -12
  87. package/src/index.native.ts +1 -1
  88. package/src/index.ts +1 -1
  89. package/src/runtime/AISDKChat.test.ts +4 -5
  90. package/src/runtime/AISDKThreads.cloud.test.ts +60 -2
  91. package/src/runtime/AISDKThreads.test.ts +88 -51
  92. package/src/runtime/AISDKThreads.ts +4 -13
  93. package/src/runtime/useAISDKRuntime.approval.integration.test.tsx +338 -0
  94. package/src/runtime/useAISDKRuntime.approval.test.tsx +476 -4
  95. package/src/runtime/useAISDKRuntime.test.ts +282 -8
  96. package/src/runtime/useAISDKRuntime.ts +274 -46
  97. package/src/runtime/useAISDKRuntime.voice.test.tsx +319 -0
  98. package/src/runtime/useChatRuntime.integration.test.tsx +56 -0
  99. package/src/runtime/useChatRuntime.test.ts +77 -5
  100. package/src/runtime/useChatRuntime.ts +6 -2
  101. package/src/runtime/useChatThread.transport.test.tsx +149 -0
  102. package/src/runtime/useChatThread.ts +77 -10
  103. package/src/runtime/useExternalHistory.test.ts +989 -3
  104. package/src/runtime/useExternalHistory.ts +390 -24
  105. package/src/runtime/useResourceCleanup.ts +41 -15
  106. package/src/tools/generativeTools.test.ts +7 -1
  107. package/src/usage.ts +1 -1
@@ -3,6 +3,9 @@
3
3
  import { act, renderHook, waitFor } from "@testing-library/react";
4
4
  import { beforeEach, describe, expect, it, vi } from "vitest";
5
5
  import { validateUIMessages } from "ai";
6
+ import { ToolResponse } from "assistant-stream";
7
+ import type { UIMessage } from "@ai-sdk/react";
8
+ import type { MessageFormatRepository } from "@assistant-ui/core";
6
9
 
7
10
  // Mock only the sibling module that requires AUI store context (not available
8
11
  // in isolation). Every other dependency — useExternalStoreRuntime,
@@ -15,6 +18,8 @@ vi.mock("./useExternalHistory", async (importOriginal) => {
15
18
  useExternalHistory: vi.fn(() => ({
16
19
  isLoading: false,
17
20
  deleteMessage: vi.fn().mockResolvedValue(undefined),
21
+ persistToolInteractions: vi.fn().mockResolvedValue(undefined),
22
+ persistToolApprovalResponses: vi.fn().mockResolvedValue(undefined),
18
23
  })),
19
24
  };
20
25
  });
@@ -80,6 +85,8 @@ describe("useAISDKRuntime", () => {
80
85
  vi.mocked(useExternalHistory).mockReturnValue({
81
86
  isLoading: false,
82
87
  deleteMessage: vi.fn().mockResolvedValue(undefined),
88
+ persistToolInteractions: vi.fn().mockResolvedValue(undefined),
89
+ persistToolApprovalResponses: vi.fn().mockResolvedValue(undefined),
83
90
  });
84
91
  });
85
92
 
@@ -119,13 +126,13 @@ describe("useAISDKRuntime", () => {
119
126
  result.current.thread.append({
120
127
  role: "user",
121
128
  content: [{ type: "text", text: "hello" }],
122
- runConfig: { custom: { model: "gpt-5.6-luna" } },
129
+ runConfig: { custom: { model: "gpt-6-luna" } },
123
130
  });
124
131
  });
125
132
 
126
133
  await waitFor(() => {
127
134
  expect(chat.sendMessage).toHaveBeenCalledWith(expect.anything(), {
128
- metadata: { custom: { model: "gpt-5.6-luna" } },
135
+ metadata: { custom: { model: "gpt-6-luna" } },
129
136
  });
130
137
  });
131
138
  });
@@ -569,7 +576,65 @@ describe("useAISDKRuntime", () => {
569
576
  ).resolves.toBeDefined();
570
577
  });
571
578
 
572
- it("forwards a successful tool result through addToolOutput, not the deprecated addToolResult", async () => {
579
+ it("cancels a pending tool call left behind a staged message", async () => {
580
+ const chat = createChatHelpers([
581
+ {
582
+ id: "a1",
583
+ role: "assistant",
584
+ parts: [
585
+ {
586
+ type: "dynamic-tool",
587
+ toolName: "mcp_search",
588
+ toolCallId: "tc-1",
589
+ state: "approval-requested",
590
+ input: { q: "hi" },
591
+ approval: { id: "appr-1" },
592
+ },
593
+ ],
594
+ },
595
+ ]);
596
+
597
+ const { result } = renderHook(() => useAISDKRuntime(chat));
598
+
599
+ await waitFor(() => {
600
+ expect(result.current.thread.getState().messages.length).toBeGreaterThan(
601
+ 0,
602
+ );
603
+ });
604
+
605
+ act(() => {
606
+ result.current.thread.append({
607
+ role: "user",
608
+ content: [{ type: "text", text: "context" }],
609
+ startRun: false,
610
+ });
611
+ });
612
+
613
+ await waitFor(() => {
614
+ expect(chat.messages).toHaveLength(2);
615
+ });
616
+
617
+ act(() => {
618
+ result.current.thread.append({
619
+ role: "user",
620
+ content: [{ type: "text", text: "what" }],
621
+ });
622
+ });
623
+
624
+ await waitFor(() => {
625
+ expect(chat.sendMessage).toHaveBeenCalledTimes(1);
626
+ });
627
+
628
+ const part = chat.messages[0].parts[0];
629
+ expect(part.state).toBe("output-error");
630
+ expect(part.approval).toBeUndefined();
631
+
632
+ await expect(
633
+ validateUIMessages({ messages: chat.messages }),
634
+ ).resolves.toBeDefined();
635
+ });
636
+
637
+ it("attaches a tool artifact to the live part and forwards its result through addToolOutput", async () => {
573
638
  const chat = createChatHelpers([
574
639
  {
575
640
  id: "a1",
@@ -585,7 +650,7 @@ describe("useAISDKRuntime", () => {
585
650
  },
586
651
  ]);
587
652
 
588
- const { result } = renderHook(() => useAISDKRuntime(chat));
653
+ const { result, rerender } = renderHook(() => useAISDKRuntime(chat));
589
654
 
590
655
  await waitFor(() => {
591
656
  expect(result.current.thread.getState().messages.length).toBeGreaterThan(
@@ -597,13 +662,26 @@ describe("useAISDKRuntime", () => {
597
662
  result.current.thread
598
663
  .getMessageById("a1")
599
664
  .getMessagePartByToolCallId("tc-1")
600
- .addToolResult({ temp: 72 });
665
+ .addToolResult(
666
+ new ToolResponse({
667
+ result: { temp: 72 },
668
+ artifact: { preview: "72°F and sunny" },
669
+ }),
670
+ );
601
671
  });
602
672
 
603
673
  await waitFor(() => {
604
674
  expect(chat.addToolOutput).toHaveBeenCalledTimes(1);
605
675
  });
606
676
 
677
+ const livePart = result.current.thread
678
+ .getMessageById("a1")
679
+ .getMessagePartByToolCallId("tc-1")
680
+ .getState();
681
+ expect(
682
+ livePart.type === "tool-call" ? livePart.artifact : undefined,
683
+ ).toEqual({ preview: "72°F and sunny" });
684
+
607
685
  expect(chat.addToolOutput).toHaveBeenCalledWith(
608
686
  expect.objectContaining({
609
687
  tool: "weather",
@@ -612,7 +690,90 @@ describe("useAISDKRuntime", () => {
612
690
  options: { metadata: undefined },
613
691
  }),
614
692
  );
693
+ expect(chat.addToolOutput.mock.calls[0]?.[0]).not.toHaveProperty(
694
+ "artifact",
695
+ );
615
696
  expect(chat.addToolResult).not.toHaveBeenCalled();
697
+ expect(
698
+ vi.mocked(useExternalHistory).mock.calls.at(-1)?.[5]?.get("tc-1"),
699
+ ).toEqual({ preview: "72°F and sunny" });
700
+
701
+ chat.messages = chat.messages.map((message: UIMessage) => ({
702
+ ...message,
703
+ }));
704
+ rerender();
705
+
706
+ await waitFor(() => {
707
+ const part = result.current.thread
708
+ .getMessageById("a1")
709
+ .getMessagePartByToolCallId("tc-1")
710
+ .getState();
711
+ expect(part.type === "tool-call" ? part.artifact : undefined).toEqual({
712
+ preview: "72°F and sunny",
713
+ });
714
+ });
715
+ expect(chat.messages[0]?.metadata).toBeUndefined();
716
+ });
717
+
718
+ it("shows recorded tool interactions without writing them to chat messages", async () => {
719
+ const chat = createChatHelpers([
720
+ {
721
+ id: "a1",
722
+ role: "assistant",
723
+ parts: [
724
+ {
725
+ type: "tool-weather",
726
+ toolCallId: "tc-1",
727
+ state: "input-available",
728
+ input: { city: "NYC" },
729
+ },
730
+ ],
731
+ },
732
+ ]);
733
+ const persistToolInteractions = vi.fn().mockResolvedValue(undefined);
734
+ vi.mocked(useExternalHistory).mockReturnValue({
735
+ isLoading: false,
736
+ deleteMessage: vi.fn().mockResolvedValue(undefined),
737
+ persistToolInteractions,
738
+ persistToolApprovalResponses: vi.fn().mockResolvedValue(undefined),
739
+ });
740
+
741
+ const { result } = renderHook(() => useAISDKRuntime(chat));
742
+
743
+ await waitFor(() => {
744
+ expect(result.current.thread.getState().messages).toHaveLength(1);
745
+ });
746
+
747
+ await act(async () => {
748
+ await result.current.thread
749
+ .getMessageById("a1")
750
+ .getMessagePartByToolCallId("tc-1").unstable_recordInteraction!({
751
+ type: "action",
752
+ payload: { refresh: true },
753
+ });
754
+ });
755
+
756
+ await waitFor(() => {
757
+ const part = result.current.thread
758
+ .getMessageById("a1")
759
+ .getMessagePartByToolCallId("tc-1")
760
+ .getState();
761
+ expect(
762
+ part.type === "tool-call" ? part.unstable_interactions : undefined,
763
+ ).toEqual({
764
+ entries: [
765
+ {
766
+ type: "action",
767
+ occurredAt: expect.any(Number),
768
+ payload: { refresh: true },
769
+ },
770
+ ],
771
+ });
772
+ });
773
+ expect(persistToolInteractions).toHaveBeenCalledExactlyOnceWith("a1");
774
+ expect(chat.messages[0]?.metadata).toBeUndefined();
775
+ expect(chat.addToolOutput).not.toHaveBeenCalled();
776
+ expect(chat.sendMessage).not.toHaveBeenCalled();
616
777
  });
617
778
 
618
779
  it("appends a new user message without sending when startRun is false", async () => {
@@ -702,6 +863,8 @@ describe("useAISDKRuntime", () => {
702
863
  vi.mocked(useExternalHistory).mockReturnValue({
703
864
  isLoading: false,
704
865
  deleteMessage,
866
+ persistToolInteractions: vi.fn().mockResolvedValue(undefined),
867
+ persistToolApprovalResponses: vi.fn().mockResolvedValue(undefined),
705
868
  });
706
869
  const chat = createChatHelpers([
707
870
  { id: "u1", role: "user", parts: [{ type: "text", text: "first" }] },
@@ -736,6 +899,115 @@ describe("useAISDKRuntime", () => {
736
899
  ]);
737
900
  });
738
901
 
902
+ it("removes tool artifacts with their deleted message", async () => {
903
+ const deleteMessage = vi.fn().mockResolvedValue(undefined);
904
+ vi.mocked(useExternalHistory).mockReturnValue({
905
+ isLoading: false,
906
+ deleteMessage,
907
+ persistToolInteractions: vi.fn().mockResolvedValue(undefined),
908
+ persistToolApprovalResponses: vi.fn().mockResolvedValue(undefined),
909
+ });
910
+ const chat = createChatHelpers([
911
+ {
912
+ id: "a1",
913
+ role: "assistant",
914
+ parts: [
915
+ {
916
+ type: "tool-weather",
917
+ toolCallId: "tc-1",
918
+ state: "input-available",
919
+ input: { city: "NYC" },
920
+ },
921
+ ],
922
+ },
923
+ ]);
924
+
925
+ const { result } = renderHook(() => useAISDKRuntime(chat));
926
+
927
+ await waitFor(() => {
928
+ expect(result.current.thread.getState().messages).toHaveLength(1);
929
+ });
930
+
931
+ act(() => {
932
+ result.current.thread
933
+ .getMessageById("a1")
934
+ .getMessagePartByToolCallId("tc-1")
935
+ .addToolResult(
936
+ new ToolResponse({
937
+ result: { temp: 72 },
938
+ artifact: { preview: "72°F and sunny" },
939
+ }),
940
+ );
941
+ });
942
+
943
+ await waitFor(() => {
944
+ expect(chat.addToolOutput).toHaveBeenCalledTimes(1);
945
+ });
946
+
947
+ const toolArtifacts = vi.mocked(useExternalHistory).mock.calls.at(-1)?.[5];
948
+ expect(toolArtifacts?.get("tc-1")).toEqual({
949
+ preview: "72°F and sunny",
950
+ });
951
+ await act(async () => {
952
+ await result.current.thread.getMessageById("a1").delete();
953
+ });
954
+
955
+ expect(deleteMessage).toHaveBeenCalledWith("a1");
956
+ expect(toolArtifacts?.has("tc-1")).toBe(false);
957
+ });
958
+
959
+ it("removes tool interactions with their deleted message", async () => {
960
+ const deleteMessage = vi.fn().mockResolvedValue(undefined);
961
+ vi.mocked(useExternalHistory).mockReturnValue({
962
+ isLoading: false,
963
+ deleteMessage,
964
+ persistToolInteractions: vi.fn().mockResolvedValue(undefined),
965
+ persistToolApprovalResponses: vi.fn().mockResolvedValue(undefined),
966
+ });
967
+ const chat = createChatHelpers([
968
+ {
969
+ id: "a1",
970
+ role: "assistant",
971
+ parts: [
972
+ {
973
+ type: "tool-weather",
974
+ toolCallId: "tc-1",
975
+ state: "output-available",
976
+ input: { city: "NYC" },
977
+ output: { temp: 72 },
978
+ },
979
+ ],
980
+ },
981
+ ]);
982
+
983
+ const { result } = renderHook(() => useAISDKRuntime(chat));
984
+
985
+ await waitFor(() => {
986
+ expect(result.current.thread.getState().messages).toHaveLength(1);
987
+ });
988
+
989
+ await act(async () => {
990
+ await result.current.thread
991
+ .getMessageById("a1")
992
+ .getMessagePartByToolCallId("tc-1").unstable_recordInteraction!({
993
+ type: "action",
994
+ payload: { type: "refresh" },
995
+ });
996
+ });
997
+
998
+ const toolInteractions = vi
999
+ .mocked(useExternalHistory)
1000
+ .mock.calls.at(-1)?.[7];
1001
+ expect(toolInteractions?.has("tc-1")).toBe(true);
1002
+
1003
+ await act(async () => {
1004
+ await result.current.thread.getMessageById("a1").delete();
1005
+ });
1006
+
1007
+ expect(deleteMessage).toHaveBeenCalledWith("a1");
1008
+ expect(toolInteractions?.has("tc-1")).toBe(false);
1009
+ });
1010
+
739
1011
  it("edit slices history to parentId and sends the edited message", async () => {
740
1012
  const chat = createChatHelpers([
741
1013
  { id: "u1", role: "user", parts: [{ type: "text", text: "first" }] },
@@ -981,7 +1253,7 @@ describe("useAISDKRuntime", () => {
981
1253
  it("imports a message tree without replacing the chat feed", async () => {
982
1254
  const chat = createChatHelpers();
983
1255
  const onBranchChange = vi.fn();
984
- const messageRepository = {
1256
+ const messageRepository: MessageFormatRepository<UIMessage> = {
985
1257
  headId: "a2",
986
1258
  messages: [
987
1259
  {
@@ -1077,7 +1349,7 @@ describe("useAISDKRuntime", () => {
1077
1349
  const chat = createChatHelpers([
1078
1350
  { id: "live", role: "user", parts: [{ type: "text", text: "keep me" }] },
1079
1351
  ]);
1080
- const messageRepository = {
1352
+ const messageRepository: MessageFormatRepository<UIMessage> = {
1081
1353
  headId: "a1",
1082
1354
  messages: [
1083
1355
  {
@@ -1115,7 +1387,9 @@ describe("useAISDKRuntime", () => {
1115
1387
 
1116
1388
  it("does not reseed when the repository object identity changes", async () => {
1117
1389
  const chat = createChatHelpers();
1118
- const makeRepository = (text: string) => ({
1390
+ const makeRepository = (
1391
+ text: string,
1392
+ ): MessageFormatRepository<UIMessage> => ({
1119
1393
  headId: "a1",
1120
1394
  messages: [
1121
1395
  {