@assistant-ui/ai-sdk 0.0.7 → 0.0.9

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 (115) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +1 -1
  3. package/dist/adapters/aiSDKFormatAdapter.d.ts +2 -2
  4. package/dist/adapters/aiSDKFormatAdapter.d.ts.map +1 -0
  5. package/dist/adapters/vercelAttachmentAdapter.d.ts +1 -3
  6. package/dist/adapters/vercelAttachmentAdapter.d.ts.map +1 -1
  7. package/dist/adapters/vercelAttachmentAdapter.js +3 -3
  8. package/dist/adapters/vercelAttachmentAdapter.js.map +1 -1
  9. package/dist/aiSDKExtras.d.ts +4 -6
  10. package/dist/aiSDKExtras.d.ts.map +1 -1
  11. package/dist/converters/convertMessage.d.ts +34 -24
  12. package/dist/converters/convertMessage.d.ts.map +1 -1
  13. package/dist/converters/convertMessage.js +56 -12
  14. package/dist/converters/convertMessage.js.map +1 -1
  15. package/dist/converters/modelContentEnvelope.d.ts +6 -7
  16. package/dist/converters/modelContentEnvelope.d.ts.map +1 -1
  17. package/dist/converters/toCreateMessage.d.ts +2 -4
  18. package/dist/converters/toCreateMessage.d.ts.map +1 -1
  19. package/dist/converters/toolOutputConversion.d.ts +20 -22
  20. package/dist/converters/toolOutputConversion.d.ts.map +1 -1
  21. package/dist/hooks.d.ts +2 -4
  22. package/dist/hooks.d.ts.map +1 -1
  23. package/dist/index.d.ts +17 -14
  24. package/dist/index.d.ts.map +1 -0
  25. package/dist/index.native.d.ts +15 -12
  26. package/dist/index.native.d.ts.map +1 -0
  27. package/dist/model-context/injectInteractableContext.d.ts +2 -4
  28. package/dist/model-context/injectInteractableContext.d.ts.map +1 -1
  29. package/dist/model-context/injectQuoteContext.d.ts +1 -3
  30. package/dist/model-context/injectQuoteContext.d.ts.map +1 -1
  31. package/dist/runtime/AISDKChat.d.ts +2 -4
  32. package/dist/runtime/AISDKChat.d.ts.map +1 -1
  33. package/dist/runtime/AISDKChat.js +1 -1
  34. package/dist/runtime/AISDKChat.js.map +1 -1
  35. package/dist/runtime/AISDKThreads.d.ts +29 -31
  36. package/dist/runtime/AISDKThreads.d.ts.map +1 -1
  37. package/dist/runtime/AISDKThreads.js +22 -23
  38. package/dist/runtime/AISDKThreads.js.map +1 -1
  39. package/dist/runtime/sdkIdentity.d.ts +1 -3
  40. package/dist/runtime/sdkIdentity.d.ts.map +1 -1
  41. package/dist/runtime/sdkIdentity.js +1 -1
  42. package/dist/runtime/useAISDKRuntime.d.ts +76 -70
  43. package/dist/runtime/useAISDKRuntime.d.ts.map +1 -1
  44. package/dist/runtime/useAISDKRuntime.js +311 -77
  45. package/dist/runtime/useAISDKRuntime.js.map +1 -1
  46. package/dist/runtime/useChatRuntime.d.ts +6 -8
  47. package/dist/runtime/useChatRuntime.d.ts.map +1 -1
  48. package/dist/runtime/useChatRuntime.js +14 -10
  49. package/dist/runtime/useChatRuntime.js.map +1 -1
  50. package/dist/runtime/useChatThread.d.ts +94 -89
  51. package/dist/runtime/useChatThread.d.ts.map +1 -1
  52. package/dist/runtime/useChatThread.js +44 -15
  53. package/dist/runtime/useChatThread.js.map +1 -1
  54. package/dist/runtime/useExternalHistory.d.ts +7 -7
  55. package/dist/runtime/useExternalHistory.d.ts.map +1 -1
  56. package/dist/runtime/useExternalHistory.js +231 -60
  57. package/dist/runtime/useExternalHistory.js.map +1 -1
  58. package/dist/runtime/useResourceCleanup.d.ts +1 -3
  59. package/dist/runtime/useResourceCleanup.d.ts.map +1 -1
  60. package/dist/runtime/useResourceCleanup.js +38 -14
  61. package/dist/runtime/useResourceCleanup.js.map +1 -1
  62. package/dist/runtime/useStreamingTiming.d.ts +3 -5
  63. package/dist/runtime/useStreamingTiming.d.ts.map +1 -1
  64. package/dist/runtime/useStreamingTiming.js +1 -1
  65. package/dist/tools/frontendTools.d.ts +21 -23
  66. package/dist/tools/frontendTools.d.ts.map +1 -1
  67. package/dist/tools/generativeTools.d.ts +24 -26
  68. package/dist/tools/generativeTools.d.ts.map +1 -1
  69. package/dist/tools/mcp-stdio.node.d.ts +2 -2
  70. package/dist/tools/mcp-stdio.node.d.ts.map +1 -0
  71. package/dist/tools/mcp-stdio.unsupported.d.ts +2 -4
  72. package/dist/tools/mcp-stdio.unsupported.d.ts.map +1 -1
  73. package/dist/transport/AssistantChatTransport.d.ts +14 -16
  74. package/dist/transport/AssistantChatTransport.d.ts.map +1 -1
  75. package/dist/transport/AssistantChatTransport.js +9 -2
  76. package/dist/transport/AssistantChatTransport.js.map +1 -1
  77. package/dist/transport/resumable.d.ts +25 -27
  78. package/dist/transport/resumable.d.ts.map +1 -1
  79. package/dist/usage.d.ts +8 -10
  80. package/dist/usage.d.ts.map +1 -1
  81. package/dist/usage.js.map +1 -1
  82. package/dist/utils/getVercelAIMessages.d.ts +2 -4
  83. package/dist/utils/getVercelAIMessages.d.ts.map +1 -1
  84. package/dist/utils/sliceMessagesUntil.d.ts +1 -3
  85. package/dist/utils/sliceMessagesUntil.d.ts.map +1 -1
  86. package/package.json +13 -9
  87. package/src/adapters/vercelAttachmentAdapter.test.ts +23 -0
  88. package/src/adapters/vercelAttachmentAdapter.ts +2 -2
  89. package/src/converters/convertMessage.test.ts +541 -3
  90. package/src/converters/convertMessage.ts +123 -15
  91. package/src/index.native.ts +1 -1
  92. package/src/index.ts +1 -1
  93. package/src/runtime/AISDKChat.ts +0 -4
  94. package/src/runtime/AISDKThreads.cloud.test.ts +60 -2
  95. package/src/runtime/AISDKThreads.test.ts +105 -46
  96. package/src/runtime/AISDKThreads.ts +1 -1
  97. package/src/runtime/useAISDKRuntime.approval.integration.test.tsx +1639 -19
  98. package/src/runtime/useAISDKRuntime.approval.test.tsx +252 -9
  99. package/src/runtime/useAISDKRuntime.fast-refresh.test.tsx +182 -0
  100. package/src/runtime/useAISDKRuntime.test.ts +275 -5
  101. package/src/runtime/useAISDKRuntime.ts +533 -60
  102. package/src/runtime/useAISDKRuntime.voice.test.tsx +50 -1
  103. package/src/runtime/useChatRuntime.fast-refresh.test.tsx +94 -0
  104. package/src/runtime/useChatRuntime.integration.test.tsx +143 -65
  105. package/src/runtime/useChatRuntime.test.ts +11 -7
  106. package/src/runtime/useChatRuntime.ts +6 -2
  107. package/src/runtime/useChatThread.test.ts +166 -2
  108. package/src/runtime/useChatThread.transport.test.tsx +152 -0
  109. package/src/runtime/useChatThread.ts +89 -22
  110. package/src/runtime/useExternalHistory.test.ts +943 -3
  111. package/src/runtime/useExternalHistory.ts +378 -15
  112. package/src/runtime/useResourceCleanup.ts +41 -15
  113. package/src/transport/AssistantChatTransport.test.ts +164 -0
  114. package/src/transport/AssistantChatTransport.ts +22 -2
  115. package/src/usage.ts +1 -1
@@ -3,6 +3,7 @@
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";
6
7
  import type { UIMessage } from "@ai-sdk/react";
7
8
  import type { MessageFormatRepository } from "@assistant-ui/core";
8
9
 
@@ -17,6 +18,8 @@ vi.mock("./useExternalHistory", async (importOriginal) => {
17
18
  useExternalHistory: vi.fn(() => ({
18
19
  isLoading: false,
19
20
  deleteMessage: vi.fn().mockResolvedValue(undefined),
21
+ persistToolInteractions: vi.fn().mockResolvedValue(undefined),
22
+ persistToolApprovalResponses: vi.fn().mockResolvedValue(undefined),
20
23
  })),
21
24
  };
22
25
  });
@@ -82,6 +85,8 @@ describe("useAISDKRuntime", () => {
82
85
  vi.mocked(useExternalHistory).mockReturnValue({
83
86
  isLoading: false,
84
87
  deleteMessage: vi.fn().mockResolvedValue(undefined),
88
+ persistToolInteractions: vi.fn().mockResolvedValue(undefined),
89
+ persistToolApprovalResponses: vi.fn().mockResolvedValue(undefined),
85
90
  });
86
91
  });
87
92
 
@@ -121,13 +126,13 @@ describe("useAISDKRuntime", () => {
121
126
  result.current.thread.append({
122
127
  role: "user",
123
128
  content: [{ type: "text", text: "hello" }],
124
- runConfig: { custom: { model: "gpt-5.6-luna" } },
129
+ runConfig: { custom: { model: "gpt-6-luna" } },
125
130
  });
126
131
  });
127
132
 
128
133
  await waitFor(() => {
129
134
  expect(chat.sendMessage).toHaveBeenCalledWith(expect.anything(), {
130
- metadata: { custom: { model: "gpt-5.6-luna" } },
135
+ metadata: { custom: { model: "gpt-6-luna" } },
131
136
  });
132
137
  });
133
138
  });
@@ -571,7 +576,65 @@ describe("useAISDKRuntime", () => {
571
576
  ).resolves.toBeDefined();
572
577
  });
573
578
 
574
- 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 () => {
575
638
  const chat = createChatHelpers([
576
639
  {
577
640
  id: "a1",
@@ -587,7 +650,7 @@ describe("useAISDKRuntime", () => {
587
650
  },
588
651
  ]);
589
652
 
590
- const { result } = renderHook(() => useAISDKRuntime(chat));
653
+ const { result, rerender } = renderHook(() => useAISDKRuntime(chat));
591
654
 
592
655
  await waitFor(() => {
593
656
  expect(result.current.thread.getState().messages.length).toBeGreaterThan(
@@ -599,13 +662,26 @@ describe("useAISDKRuntime", () => {
599
662
  result.current.thread
600
663
  .getMessageById("a1")
601
664
  .getMessagePartByToolCallId("tc-1")
602
- .addToolResult({ temp: 72 });
665
+ .addToolResult(
666
+ new ToolResponse({
667
+ result: { temp: 72 },
668
+ artifact: { preview: "72°F and sunny" },
669
+ }),
670
+ );
603
671
  });
604
672
 
605
673
  await waitFor(() => {
606
674
  expect(chat.addToolOutput).toHaveBeenCalledTimes(1);
607
675
  });
608
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
+
609
685
  expect(chat.addToolOutput).toHaveBeenCalledWith(
610
686
  expect.objectContaining({
611
687
  tool: "weather",
@@ -614,7 +690,90 @@ describe("useAISDKRuntime", () => {
614
690
  options: { metadata: undefined },
615
691
  }),
616
692
  );
693
+ expect(chat.addToolOutput.mock.calls[0]?.[0]).not.toHaveProperty(
694
+ "artifact",
695
+ );
617
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();
618
777
  });
619
778
 
620
779
  it("appends a new user message without sending when startRun is false", async () => {
@@ -704,6 +863,8 @@ describe("useAISDKRuntime", () => {
704
863
  vi.mocked(useExternalHistory).mockReturnValue({
705
864
  isLoading: false,
706
865
  deleteMessage,
866
+ persistToolInteractions: vi.fn().mockResolvedValue(undefined),
867
+ persistToolApprovalResponses: vi.fn().mockResolvedValue(undefined),
707
868
  });
708
869
  const chat = createChatHelpers([
709
870
  { id: "u1", role: "user", parts: [{ type: "text", text: "first" }] },
@@ -738,6 +899,115 @@ describe("useAISDKRuntime", () => {
738
899
  ]);
739
900
  });
740
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
+
741
1011
  it("edit slices history to parentId and sends the edited message", async () => {
742
1012
  const chat = createChatHelpers([
743
1013
  { id: "u1", role: "user", parts: [{ type: "text", text: "first" }] },