@assistant-ui/react 0.14.4 → 0.14.6

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 (97) hide show
  1. package/dist/index.d.ts +2 -0
  2. package/dist/index.d.ts.map +1 -1
  3. package/dist/index.js +1 -0
  4. package/dist/index.js.map +1 -1
  5. package/dist/legacy-runtime/AssistantRuntimeProvider.d.ts +8 -2
  6. package/dist/legacy-runtime/AssistantRuntimeProvider.d.ts.map +1 -1
  7. package/dist/legacy-runtime/AssistantRuntimeProvider.js.map +1 -1
  8. package/dist/legacy-runtime/hooks/AssistantContext.d.ts +2 -2
  9. package/dist/legacy-runtime/hooks/AssistantContext.js +1 -1
  10. package/dist/legacy-runtime/hooks/AttachmentContext.d.ts +2 -2
  11. package/dist/legacy-runtime/hooks/AttachmentContext.js +1 -1
  12. package/dist/legacy-runtime/hooks/ComposerContext.d.ts +2 -2
  13. package/dist/legacy-runtime/hooks/ComposerContext.js +1 -1
  14. package/dist/legacy-runtime/hooks/MessageContext.d.ts +3 -3
  15. package/dist/legacy-runtime/hooks/MessageContext.js +2 -2
  16. package/dist/legacy-runtime/hooks/MessagePartContext.d.ts +2 -2
  17. package/dist/legacy-runtime/hooks/MessagePartContext.js +1 -1
  18. package/dist/legacy-runtime/hooks/ThreadContext.d.ts +4 -4
  19. package/dist/legacy-runtime/hooks/ThreadContext.js +2 -2
  20. package/dist/legacy-runtime/hooks/ThreadListItemContext.d.ts +2 -2
  21. package/dist/legacy-runtime/hooks/ThreadListItemContext.js +1 -1
  22. package/dist/mcp-apps/McpAppRenderer.d.ts +8 -0
  23. package/dist/mcp-apps/McpAppRenderer.d.ts.map +1 -1
  24. package/dist/mcp-apps/McpAppRenderer.js +8 -0
  25. package/dist/mcp-apps/McpAppRenderer.js.map +1 -1
  26. package/dist/mcp-apps/McpAppsRemoteHost.d.ts +7 -0
  27. package/dist/mcp-apps/McpAppsRemoteHost.d.ts.map +1 -1
  28. package/dist/mcp-apps/McpAppsRemoteHost.js +7 -0
  29. package/dist/mcp-apps/McpAppsRemoteHost.js.map +1 -1
  30. package/dist/mcp-apps/bridge.d.ts.map +1 -1
  31. package/dist/mcp-apps/bridge.js +15 -2
  32. package/dist/mcp-apps/bridge.js.map +1 -1
  33. package/dist/mcp-apps/utils.d.ts +7 -0
  34. package/dist/mcp-apps/utils.d.ts.map +1 -1
  35. package/dist/mcp-apps/utils.js +7 -0
  36. package/dist/mcp-apps/utils.js.map +1 -1
  37. package/dist/primitives/actionBar/ActionBarCopy.d.ts.map +1 -1
  38. package/dist/primitives/actionBar/ActionBarCopy.js +6 -1
  39. package/dist/primitives/actionBar/ActionBarCopy.js.map +1 -1
  40. package/dist/primitives/messagePart/MessagePartInProgress.d.ts +1 -5
  41. package/dist/primitives/messagePart/MessagePartInProgress.d.ts.map +1 -1
  42. package/dist/primitives/messagePart/MessagePartInProgress.js +1 -7
  43. package/dist/primitives/messagePart/MessagePartInProgress.js.map +1 -1
  44. package/dist/primitives/messagePart/useMessagePartData.d.ts +16 -0
  45. package/dist/primitives/messagePart/useMessagePartData.d.ts.map +1 -1
  46. package/dist/primitives/messagePart/useMessagePartData.js +16 -0
  47. package/dist/primitives/messagePart/useMessagePartData.js.map +1 -1
  48. package/dist/primitives/messagePart/useMessagePartFile.d.ts +15 -0
  49. package/dist/primitives/messagePart/useMessagePartFile.d.ts.map +1 -1
  50. package/dist/primitives/messagePart/useMessagePartFile.js +15 -0
  51. package/dist/primitives/messagePart/useMessagePartFile.js.map +1 -1
  52. package/dist/primitives/messagePart/useMessagePartImage.d.ts +15 -0
  53. package/dist/primitives/messagePart/useMessagePartImage.d.ts.map +1 -1
  54. package/dist/primitives/messagePart/useMessagePartImage.js +15 -0
  55. package/dist/primitives/messagePart/useMessagePartImage.js.map +1 -1
  56. package/dist/primitives/messagePart/useMessagePartReasoning.d.ts +15 -0
  57. package/dist/primitives/messagePart/useMessagePartReasoning.d.ts.map +1 -1
  58. package/dist/primitives/messagePart/useMessagePartReasoning.js +15 -0
  59. package/dist/primitives/messagePart/useMessagePartReasoning.js.map +1 -1
  60. package/dist/primitives/messagePart/useMessagePartSource.d.ts +15 -0
  61. package/dist/primitives/messagePart/useMessagePartSource.d.ts.map +1 -1
  62. package/dist/primitives/messagePart/useMessagePartSource.js +15 -0
  63. package/dist/primitives/messagePart/useMessagePartSource.js.map +1 -1
  64. package/dist/primitives/messagePart/useMessagePartText.d.ts +15 -0
  65. package/dist/primitives/messagePart/useMessagePartText.d.ts.map +1 -1
  66. package/dist/primitives/messagePart/useMessagePartText.js +15 -0
  67. package/dist/primitives/messagePart/useMessagePartText.js.map +1 -1
  68. package/dist/primitives/thread/useThreadViewportAutoScroll.d.ts +1 -1
  69. package/dist/primitives/thread/useThreadViewportAutoScroll.d.ts.map +1 -1
  70. package/dist/primitives/thread/useThreadViewportAutoScroll.js +50 -27
  71. package/dist/primitives/thread/useThreadViewportAutoScroll.js.map +1 -1
  72. package/package.json +7 -7
  73. package/src/index.ts +6 -0
  74. package/src/legacy-runtime/AssistantRuntimeProvider.tsx +8 -2
  75. package/src/legacy-runtime/hooks/AssistantContext.ts +2 -2
  76. package/src/legacy-runtime/hooks/AttachmentContext.ts +2 -2
  77. package/src/legacy-runtime/hooks/ComposerContext.ts +2 -2
  78. package/src/legacy-runtime/hooks/MessageContext.ts +3 -3
  79. package/src/legacy-runtime/hooks/MessagePartContext.ts +2 -2
  80. package/src/legacy-runtime/hooks/ThreadContext.ts +4 -4
  81. package/src/legacy-runtime/hooks/ThreadListItemContext.ts +2 -2
  82. package/src/legacy-runtime/runtime-cores/assistant-transport/useToolInvocations.test.ts +254 -0
  83. package/src/mcp-apps/McpAppRenderer.tsx +8 -0
  84. package/src/mcp-apps/McpAppsRemoteHost.ts +7 -0
  85. package/src/mcp-apps/bridge.ts +18 -2
  86. package/src/mcp-apps/utils.ts +7 -0
  87. package/src/primitives/actionBar/ActionBarCopy.tsx +6 -1
  88. package/src/primitives/messagePart/MessagePartInProgress.ts +1 -17
  89. package/src/primitives/messagePart/useMessagePartData.ts +16 -0
  90. package/src/primitives/messagePart/useMessagePartFile.ts +15 -0
  91. package/src/primitives/messagePart/useMessagePartImage.ts +15 -0
  92. package/src/primitives/messagePart/useMessagePartReasoning.ts +15 -0
  93. package/src/primitives/messagePart/useMessagePartSource.ts +15 -0
  94. package/src/primitives/messagePart/useMessagePartText.ts +15 -0
  95. package/src/primitives/thread/useThreadViewportAutoScroll.test.tsx +320 -0
  96. package/src/primitives/thread/useThreadViewportAutoScroll.ts +59 -29
  97. package/src/tests/BaseComposerRuntimeCore.test.ts +1 -1
@@ -10,12 +10,18 @@ import { DevToolsProviderApi } from "../devtools/DevToolsHooks";
10
10
  export namespace AssistantRuntimeProvider {
11
11
  export type Props = PropsWithChildren<{
12
12
  /**
13
- * The runtime to provide to the rest of your app.
13
+ * The assistant runtime to expose to descendants. Build one with
14
+ * `useLocalRuntime`, `useExternalStoreRuntime`, or
15
+ * `useAssistantTransportRuntime`.
14
16
  */
15
17
  runtime: AssistantRuntime;
16
18
 
17
19
  /**
18
- * The aui instance to extend. If not provided, a new aui instance will be created.
20
+ * Optional parent `AssistantClient` whose scopes are inherited by the
21
+ * client created for this runtime. Use this when nesting an
22
+ * `AssistantRuntimeProvider` inside another assistant context. Omit this
23
+ * prop when there is no parent client.
24
+ * @defaultValue undefined
19
25
  */
20
26
  aui?: AssistantClient;
21
27
  }>;
@@ -6,7 +6,7 @@ import type { ThreadListRuntime } from "../runtime/ThreadListRuntime";
6
6
  import { createStateHookForRuntime } from "../../context/react/utils/createStateHookForRuntime";
7
7
 
8
8
  /**
9
- * @deprecated Use `useAui()` instead. See migration guide: https://assistant-ui.com/docs/migrations/v0-12
9
+ * @deprecated Use {@link useAui} instead. See the {@link https://assistant-ui.com/docs/migrations/v0-12 migration guide}.
10
10
  *
11
11
  * Hook to access the AssistantRuntime from the current context.
12
12
  *
@@ -62,6 +62,6 @@ const useThreadListRuntime = (opt: {
62
62
  }): ThreadListRuntime | null => useAssistantRuntime(opt)?.threads ?? null;
63
63
 
64
64
  /**
65
- * @deprecated Use `useAuiState((s) => s.threads)` instead. See migration guide: https://assistant-ui.com/docs/migrations/v0-12
65
+ * @deprecated Use {@link useAuiState}: `useAuiState((s) => s.threads)`. See the {@link https://assistant-ui.com/docs/migrations/v0-12 migration guide}.
66
66
  */
67
67
  export const useThreadList = createStateHookForRuntime(useThreadListRuntime);
@@ -5,7 +5,7 @@ import { createStateHookForRuntime } from "../../context/react/utils/createState
5
5
  import { useAui, useAuiState } from "@assistant-ui/store";
6
6
 
7
7
  /**
8
- * @deprecated Use `useAui()` with `aui.attachment()` instead. See migration guide: https://assistant-ui.com/docs/migrations/v0-12
8
+ * @deprecated Use {@link useAui} with `aui.attachment()` instead. See the {@link https://assistant-ui.com/docs/migrations/v0-12 migration guide}.
9
9
  */
10
10
  export function useAttachmentRuntime(options?: {
11
11
  optional?: false | undefined;
@@ -84,7 +84,7 @@ export function useMessageAttachmentRuntime(options?: {
84
84
  }
85
85
 
86
86
  /**
87
- * @deprecated Use `useAuiState((s) => s.attachment)` instead. See migration guide: https://assistant-ui.com/docs/migrations/v0-12
87
+ * @deprecated Use {@link useAuiState}: `useAuiState((s) => s.attachment)`. See the {@link https://assistant-ui.com/docs/migrations/v0-12 migration guide}.
88
88
  */
89
89
  export const useAttachment = createStateHookForRuntime(useAttachmentRuntime);
90
90
 
@@ -5,7 +5,7 @@ import type { ComposerRuntime } from "../runtime/ComposerRuntime";
5
5
  import { createStateHookForRuntime } from "../../context/react/utils/createStateHookForRuntime";
6
6
 
7
7
  /**
8
- * @deprecated Use `useAui()` with `aui.composer()` instead. See migration guide: https://assistant-ui.com/docs/migrations/v0-12
8
+ * @deprecated Use {@link useAui} with `aui.composer()` instead. See the {@link https://assistant-ui.com/docs/migrations/v0-12 migration guide}.
9
9
  *
10
10
  * Hook to access the ComposerRuntime from the current context.
11
11
  *
@@ -87,7 +87,7 @@ export function useComposerRuntime(options?: {
87
87
  }
88
88
 
89
89
  /**
90
- * @deprecated Use `useAuiState((s) => s.composer)` instead. See migration guide: https://assistant-ui.com/docs/migrations/v0-12
90
+ * @deprecated Use {@link useAuiState}: `useAuiState((s) => s.composer)`. See the {@link https://assistant-ui.com/docs/migrations/v0-12 migration guide}.
91
91
  *
92
92
  * Hook to access the current composer state.
93
93
  *
@@ -6,7 +6,7 @@ import { createStateHookForRuntime } from "../../context/react/utils/createState
6
6
  import type { EditComposerRuntime } from "@assistant-ui/core";
7
7
 
8
8
  /**
9
- * @deprecated Use `useAui()` with `aui.message()` instead. See migration guide: https://assistant-ui.com/docs/migrations/v0-12
9
+ * @deprecated Use {@link useAui} with `aui.message()` instead. See the {@link https://assistant-ui.com/docs/migrations/v0-12 migration guide}.
10
10
  *
11
11
  * Hook to access the MessageRuntime from the current context.
12
12
  *
@@ -76,7 +76,7 @@ export function useMessageRuntime(options?: {
76
76
  }
77
77
 
78
78
  /**
79
- * @deprecated Use `useAuiState((s) => s.message)` instead. See migration guide: https://assistant-ui.com/docs/migrations/v0-12
79
+ * @deprecated Use {@link useAuiState}: `useAuiState((s) => s.message)`. See the {@link https://assistant-ui.com/docs/migrations/v0-12 migration guide}.
80
80
  *
81
81
  * Hook to access the current message state.
82
82
  *
@@ -120,7 +120,7 @@ const useEditComposerRuntime = (opt: {
120
120
  }): EditComposerRuntime | null => useMessageRuntime(opt)?.composer ?? null;
121
121
 
122
122
  /**
123
- * @deprecated Use `useAuiState((s) => s.message.composer)` instead. See migration guide: https://assistant-ui.com/docs/migrations/v0-12
123
+ * @deprecated Use {@link useAuiState}: `useAuiState((s) => s.message.composer)`. See the {@link https://assistant-ui.com/docs/migrations/v0-12 migration guide}.
124
124
  */
125
125
  export const useEditComposer = createStateHookForRuntime(
126
126
  useEditComposerRuntime,
@@ -5,7 +5,7 @@ import { createStateHookForRuntime } from "../../context/react/utils/createState
5
5
  import { useAui, useAuiState } from "@assistant-ui/store";
6
6
 
7
7
  /**
8
- * @deprecated Use `useAui()` with `aui.part()` instead. See migration guide: https://assistant-ui.com/docs/migrations/v0-12
8
+ * @deprecated Use {@link useAui} with `aui.part()` instead. See the {@link https://assistant-ui.com/docs/migrations/v0-12 migration guide}.
9
9
  */
10
10
  export function useMessagePartRuntime(options?: {
11
11
  optional?: false | undefined;
@@ -27,6 +27,6 @@ export function useMessagePartRuntime(options?: {
27
27
  }
28
28
 
29
29
  /**
30
- * @deprecated Use `useAuiState((s) => s.part)` instead. See migration guide: https://assistant-ui.com/docs/migrations/v0-12
30
+ * @deprecated Use {@link useAuiState}: `useAuiState((s) => s.part)`. See the {@link https://assistant-ui.com/docs/migrations/v0-12 migration guide}.
31
31
  */
32
32
  export const useMessagePart = createStateHookForRuntime(useMessagePartRuntime);
@@ -8,7 +8,7 @@ import type { ThreadComposerRuntime } from "@assistant-ui/core";
8
8
  import { useAui, useAuiEvent, useAuiState } from "@assistant-ui/store";
9
9
 
10
10
  /**
11
- * @deprecated Use `useAui()` with `aui.thread()` instead. See migration guide: https://assistant-ui.com/docs/migrations/v0-12
11
+ * @deprecated Use {@link useAui} with `aui.thread()` instead. See the {@link https://assistant-ui.com/docs/migrations/v0-12 migration guide}.
12
12
  *
13
13
  * Hook to access the ThreadRuntime from the current context.
14
14
  *
@@ -58,7 +58,7 @@ export function useThreadRuntime(options?: { optional?: boolean | undefined }) {
58
58
  }
59
59
 
60
60
  /**
61
- * @deprecated Use `useAuiState((s) => s.thread)` instead. See migration guide: https://assistant-ui.com/docs/migrations/v0-12
61
+ * @deprecated Use {@link useAuiState}: `useAuiState((s) => s.thread)`. See the {@link https://assistant-ui.com/docs/migrations/v0-12 migration guide}.
62
62
  *
63
63
  * Hook to access the current thread state.
64
64
  *
@@ -92,14 +92,14 @@ const useThreadComposerRuntime = (opt: {
92
92
  }): ThreadComposerRuntime | null => useThreadRuntime(opt)?.composer ?? null;
93
93
 
94
94
  /**
95
- * @deprecated Use `useAuiState((s) => s.thread.composer)` instead. See migration guide: https://assistant-ui.com/docs/migrations/v0-12
95
+ * @deprecated Use {@link useAuiState}: `useAuiState((s) => s.thread.composer)`. See the {@link https://assistant-ui.com/docs/migrations/v0-12 migration guide}.
96
96
  */
97
97
  export const useThreadComposer = createStateHookForRuntime(
98
98
  useThreadComposerRuntime,
99
99
  );
100
100
 
101
101
  /**
102
- * @deprecated Use `useAuiState((s) => s.thread.modelContext)` instead. See migration guide: https://assistant-ui.com/docs/migrations/v0-12
102
+ * @deprecated Use {@link useAuiState}: `useAuiState((s) => s.thread.modelContext)`. See the {@link https://assistant-ui.com/docs/migrations/v0-12 migration guide}.
103
103
  */
104
104
  export function useThreadModelContext(options?: {
105
105
  optional?: false | undefined;
@@ -5,7 +5,7 @@ import { createStateHookForRuntime } from "../../context/react/utils/createState
5
5
  import { useAui, useAuiState } from "@assistant-ui/store";
6
6
 
7
7
  /**
8
- * @deprecated Use `useAui()` with `aui.threadListItem()` instead. See migration guide: https://assistant-ui.com/docs/migrations/v0-12
8
+ * @deprecated Use {@link useAui} with `aui.threadListItem()` instead. See the {@link https://assistant-ui.com/docs/migrations/v0-12 migration guide}.
9
9
  */
10
10
  export function useThreadListItemRuntime(options?: {
11
11
  optional?: false | undefined;
@@ -29,7 +29,7 @@ export function useThreadListItemRuntime(options?: {
29
29
  }
30
30
 
31
31
  /**
32
- * @deprecated Use `useAuiState((s) => s.threadListItem)` instead. See migration guide: https://assistant-ui.com/docs/migrations/v0-12
32
+ * @deprecated Use {@link useAuiState}: `useAuiState((s) => s.threadListItem)`. See the {@link https://assistant-ui.com/docs/migrations/v0-12 migration guide}.
33
33
  */
34
34
  export const useThreadListItem = createStateHookForRuntime(
35
35
  useThreadListItemRuntime,
@@ -635,4 +635,258 @@ describe("useToolInvocations", () => {
635
635
  expect(onResult).not.toHaveBeenCalled();
636
636
  });
637
637
  });
638
+
639
+ it("fires streamCall for already-resolved tool calls loaded after the initial snapshot", async () => {
640
+ const execute = vi.fn(async () => ({ forecast: "ok" }));
641
+ const streamCall = vi.fn();
642
+ const getTools = () => ({
643
+ weatherSearch: {
644
+ parameters: { type: "object", properties: {} },
645
+ execute,
646
+ streamCall,
647
+ } satisfies Tool,
648
+ });
649
+ const onResult = vi.fn();
650
+ const setToolStatuses = vi.fn();
651
+
652
+ const { rerender } = renderHook(
653
+ ({ state }: { state: AssistantTransportState }) =>
654
+ useToolInvocations({
655
+ state,
656
+ getTools,
657
+ onResult,
658
+ setToolStatuses,
659
+ }),
660
+ {
661
+ initialProps: {
662
+ state: createState([]),
663
+ },
664
+ },
665
+ );
666
+
667
+ act(() => {
668
+ rerender({
669
+ state: createState([
670
+ createAssistantMessage(
671
+ '{"query":"London"}',
672
+ { query: "London" },
673
+ { result: { source: "history" } },
674
+ ),
675
+ ]),
676
+ });
677
+ });
678
+
679
+ await waitFor(() => {
680
+ expect(streamCall).toHaveBeenCalledTimes(1);
681
+ });
682
+
683
+ const [reader] = streamCall.mock.calls[0]!;
684
+ await expect(reader.args.get("query")).resolves.toBe("London");
685
+ const response = await reader.response.get();
686
+ expect(response.result).toEqual({ source: "history" });
687
+
688
+ expect(execute).not.toHaveBeenCalled();
689
+ expect(onResult).not.toHaveBeenCalled();
690
+ expect(setToolStatuses).not.toHaveBeenCalled();
691
+ });
692
+
693
+ it("does not fire streamCall for tool calls present in the initial snapshot", async () => {
694
+ const streamCall = vi.fn();
695
+ const getTools = () => ({
696
+ weatherSearch: {
697
+ parameters: { type: "object", properties: {} },
698
+ streamCall,
699
+ } satisfies Tool,
700
+ });
701
+ const onResult = vi.fn();
702
+ const setToolStatuses = vi.fn();
703
+
704
+ renderHook(
705
+ ({ state }: { state: AssistantTransportState }) =>
706
+ useToolInvocations({
707
+ state,
708
+ getTools,
709
+ onResult,
710
+ setToolStatuses,
711
+ }),
712
+ {
713
+ initialProps: {
714
+ state: createState([
715
+ createAssistantMessage(
716
+ '{"query":"London"}',
717
+ { query: "London" },
718
+ { result: { source: "history" } },
719
+ ),
720
+ ]),
721
+ },
722
+ },
723
+ );
724
+
725
+ await act(async () => {});
726
+ expect(streamCall).not.toHaveBeenCalled();
727
+ expect(onResult).not.toHaveBeenCalled();
728
+ });
729
+
730
+ it("promotes an in-progress tool call from the initial snapshot when it changes", async () => {
731
+ const execute = vi.fn(async () => ({ forecast: "ok" }));
732
+ const streamCall = vi.fn();
733
+ const getTools = () => ({
734
+ weatherSearch: {
735
+ parameters: { type: "object", properties: {} },
736
+ execute,
737
+ streamCall,
738
+ } satisfies Tool,
739
+ });
740
+ const onResult = vi.fn();
741
+ const setToolStatuses = vi.fn();
742
+
743
+ const { rerender } = renderHook(
744
+ ({ state }: { state: AssistantTransportState }) =>
745
+ useToolInvocations({
746
+ state,
747
+ getTools,
748
+ onResult,
749
+ setToolStatuses,
750
+ }),
751
+ {
752
+ initialProps: {
753
+ state: createState([
754
+ createAssistantMessage('{"query":"Lon', { query: "Lon" }),
755
+ ]),
756
+ },
757
+ },
758
+ );
759
+
760
+ await act(async () => {});
761
+ expect(streamCall).not.toHaveBeenCalled();
762
+
763
+ act(() => {
764
+ rerender({
765
+ state: createState([
766
+ createAssistantMessage(
767
+ '{"query":"London"}',
768
+ { query: "London" },
769
+ { result: { source: "history" } },
770
+ ),
771
+ ]),
772
+ });
773
+ });
774
+
775
+ await waitFor(() => {
776
+ expect(streamCall).toHaveBeenCalledTimes(1);
777
+ });
778
+
779
+ const [reader] = streamCall.mock.calls[0]!;
780
+ await expect(reader.args.get("query")).resolves.toBe("London");
781
+ const response = await reader.response.get();
782
+ expect(response.result).toEqual({ source: "history" });
783
+
784
+ expect(execute).not.toHaveBeenCalled();
785
+ expect(onResult).not.toHaveBeenCalled();
786
+ });
787
+
788
+ it("does not re-fire streamCall when an initial-snapshot tool call is unchanged in later snapshots", async () => {
789
+ const streamCall = vi.fn();
790
+ const getTools = () => ({
791
+ weatherSearch: {
792
+ parameters: { type: "object", properties: {} },
793
+ streamCall,
794
+ } satisfies Tool,
795
+ });
796
+ const onResult = vi.fn();
797
+ const setToolStatuses = vi.fn();
798
+
799
+ const { rerender } = renderHook(
800
+ ({ state }: { state: AssistantTransportState }) =>
801
+ useToolInvocations({
802
+ state,
803
+ getTools,
804
+ onResult,
805
+ setToolStatuses,
806
+ }),
807
+ {
808
+ initialProps: {
809
+ state: createState([
810
+ createAssistantMessage(
811
+ '{"query":"London"}',
812
+ { query: "London" },
813
+ { result: { source: "history" } },
814
+ ),
815
+ ]),
816
+ },
817
+ },
818
+ );
819
+
820
+ act(() => {
821
+ rerender({
822
+ state: createState([
823
+ createAssistantMessage(
824
+ '{"query":"London"}',
825
+ { query: "London" },
826
+ { result: { source: "history" } },
827
+ ),
828
+ ]),
829
+ });
830
+ });
831
+
832
+ await act(async () => {});
833
+ expect(streamCall).not.toHaveBeenCalled();
834
+ });
835
+
836
+ it("does not emit a cancellation onResult for pre-resolved tool calls aborted by reset", async () => {
837
+ const streamCall = vi.fn();
838
+ const getTools = () => ({
839
+ weatherSearch: {
840
+ parameters: { type: "object", properties: {} },
841
+ execute: vi.fn(async () => ({ forecast: "ok" })),
842
+ streamCall,
843
+ } satisfies Tool,
844
+ });
845
+ const onResult = vi.fn();
846
+ const setToolStatuses = vi.fn();
847
+
848
+ const { result, rerender } = renderHook(
849
+ ({ state }: { state: AssistantTransportState }) =>
850
+ useToolInvocations({
851
+ state,
852
+ getTools,
853
+ onResult,
854
+ setToolStatuses,
855
+ }),
856
+ {
857
+ initialProps: {
858
+ state: createState([]),
859
+ },
860
+ },
861
+ );
862
+
863
+ act(() => {
864
+ rerender({
865
+ state: createState([
866
+ createAssistantMessage(
867
+ '{"query":"London"}',
868
+ { query: "London" },
869
+ { result: { source: "history" } },
870
+ ),
871
+ ]),
872
+ });
873
+ });
874
+
875
+ await waitFor(() => {
876
+ expect(streamCall).toHaveBeenCalledTimes(1);
877
+ });
878
+
879
+ act(() => {
880
+ result.current.reset();
881
+ });
882
+
883
+ // Flush microtasks through the executor's abort race + the stream
884
+ // pipeline so any cancellation `result` chunk has a chance to land
885
+ // before we assert it didn't.
886
+ for (let i = 0; i < 5; i++) {
887
+ await act(async () => {});
888
+ }
889
+
890
+ expect(onResult).not.toHaveBeenCalled();
891
+ });
638
892
  });
@@ -192,6 +192,14 @@ function InlineRenderer({
192
192
  );
193
193
  }
194
194
 
195
+ /**
196
+ * Creates a tool-call renderer for MCP Apps embedded in assistant messages.
197
+ *
198
+ * Compose this into the `Tools` resource through its `mcpApp` option. When a
199
+ * tool-call part carries `mcp.app` metadata for a `ui://` resource, the
200
+ * renderer loads that resource from the configured host and displays it in a
201
+ * sandboxed frame.
202
+ */
195
203
  export const McpAppRenderer = resource(
196
204
  (
197
205
  options: McpAppRendererOptions,
@@ -26,6 +26,13 @@ async function postToHost(
26
26
  return res.json();
27
27
  }
28
28
 
29
+ /**
30
+ * Creates the default HTTP host for MCP App widgets.
31
+ *
32
+ * The host POSTs widget requests to the configured route as `{ method,
33
+ * params }`, using the method names expected by the assistant-ui MCP Apps
34
+ * guide.
35
+ */
29
36
  export const McpAppsRemoteHost = resource(
30
37
  (options: McpAppsRemoteHostOptions): McpAppsHost => {
31
38
  const optionsRef = tapRef(options);
@@ -42,6 +42,22 @@ const DEFAULT_HOST_INFO: McpAppHostInfo = {
42
42
  version: "0.1",
43
43
  };
44
44
 
45
+ // Accept both the legacy method names and the MCP-UI 2026-01-26 names that
46
+ // `ui/*` capable widgets (e.g. xmcp's host-bridge) emit. Normalize on input
47
+ // so downstream switch statements only need to know the legacy names.
48
+ const METHOD_ALIASES: Record<string, string> = {
49
+ "ui/notifications/initialized": "notifications/initialized",
50
+ "ui/notifications/size-changed": "notifications/size_changed",
51
+ "ui/request-display-mode": "requestDisplayMode",
52
+ "ui/open-link": "openLink",
53
+ "ui/update-model-context": "updateModelContext",
54
+ "ui/message": "sendMessage",
55
+ "notifications/message": "notifications/log",
56
+ };
57
+
58
+ const normalizeMethod = (method: string): string =>
59
+ METHOD_ALIASES[method] ?? method;
60
+
45
61
  const JSONRPC_ERROR = {
46
62
  parseError: -32700,
47
63
  invalidRequest: -32600,
@@ -118,7 +134,7 @@ export function createMcpAppBridge(
118
134
  try {
119
135
  const params = req.params;
120
136
 
121
- switch (req.method) {
137
+ switch (normalizeMethod(req.method)) {
122
138
  case "ui/initialize": {
123
139
  respond(req.id, {
124
140
  result: {
@@ -356,7 +372,7 @@ export function createMcpAppBridge(
356
372
  };
357
373
 
358
374
  const handleNotification = (note: McpAppJsonRpcNotification) => {
359
- switch (note.method) {
375
+ switch (normalizeMethod(note.method)) {
360
376
  case "notifications/initialized": {
361
377
  handlers.onInitialized?.();
362
378
  return;
@@ -6,6 +6,13 @@ import {
6
6
 
7
7
  type ToolPartLike = Pick<ToolCallMessagePart, "mcp">;
8
8
 
9
+ /**
10
+ * Returns MCP app metadata for a tool-call part that points at a `ui://`
11
+ * resource.
12
+ *
13
+ * Returns `undefined` when the part has no MCP app metadata or the metadata
14
+ * does not reference an assistant-ui MCP app resource.
15
+ */
9
16
  export function getMcpAppFromToolPart(
10
17
  part: ToolPartLike,
11
18
  ): McpAppMetadata | undefined {
@@ -38,7 +38,12 @@ const useActionBarPrimitiveCopy = ({
38
38
  } = {}) => {
39
39
  const { copy, disabled } = useActionBarCopy({
40
40
  copiedDuration,
41
- copyToClipboard: (text) => navigator.clipboard.writeText(text),
41
+ copyToClipboard: (text) => {
42
+ if (typeof navigator === "undefined" || !navigator.clipboard) {
43
+ return Promise.reject(new Error("Clipboard API is unavailable"));
44
+ }
45
+ return navigator.clipboard.writeText(text);
46
+ },
42
47
  });
43
48
  if (disabled) return null;
44
49
  return copy;
@@ -1,19 +1,3 @@
1
1
  "use client";
2
2
 
3
- import type { FC, PropsWithChildren } from "react";
4
- import { useAuiState } from "@assistant-ui/store";
5
-
6
- export namespace MessagePartPrimitiveInProgress {
7
- export type Props = PropsWithChildren;
8
- }
9
-
10
- // TODO should this be renamed to IsRunning?
11
- export const MessagePartPrimitiveInProgress: FC<
12
- MessagePartPrimitiveInProgress.Props
13
- > = ({ children }) => {
14
- const isInProgress = useAuiState((s) => s.part.status.type === "running");
15
-
16
- return isInProgress ? children : null;
17
- };
18
-
19
- MessagePartPrimitiveInProgress.displayName = "MessagePartPrimitive.InProgress";
3
+ export { MessagePartPrimitiveInProgress } from "@assistant-ui/core/react";
@@ -3,6 +3,22 @@
3
3
  import type { DataMessagePart } from "@assistant-ui/core";
4
4
  import { useAuiState } from "@assistant-ui/store";
5
5
 
6
+ /**
7
+ * @deprecated Use {@link useAuiState} to select and narrow `s.part`.
8
+ * Return `null` for optional rendering, or throw inside the selector to
9
+ * preserve the old hook's strict behavior.
10
+ *
11
+ * @example
12
+ * ```tsx
13
+ * const part = useAuiState((s) =>
14
+ * s.part.type === "data" && (!name || s.part.name === name)
15
+ * ? s.part
16
+ * : null,
17
+ * );
18
+ * ```
19
+ *
20
+ * See the {@link https://assistant-ui.com/docs/migrations/v0-12 migration guide}.
21
+ */
6
22
  export const useMessagePartData = <T = any>(name?: string) => {
7
23
  const part = useAuiState((s) => {
8
24
  if (s.part.type !== "data") {
@@ -3,6 +3,21 @@
3
3
  import type { FileMessagePart, MessagePartState } from "@assistant-ui/core";
4
4
  import { useAuiState } from "@assistant-ui/store";
5
5
 
6
+ /**
7
+ * @deprecated Use {@link useAuiState} to select and narrow `s.part`.
8
+ * Return `null` for optional rendering, or throw inside the selector to
9
+ * preserve the old hook's strict behavior.
10
+ *
11
+ * @example
12
+ * ```tsx
13
+ * const file = useAuiState((s) => {
14
+ * if (s.part.type !== "file") return null;
15
+ * return s.part;
16
+ * });
17
+ * ```
18
+ *
19
+ * See the {@link https://assistant-ui.com/docs/migrations/v0-12 migration guide}.
20
+ */
6
21
  export const useMessagePartFile = () => {
7
22
  const file = useAuiState((s) => {
8
23
  if (s.part.type !== "file")
@@ -3,6 +3,21 @@
3
3
  import type { ImageMessagePart, MessagePartState } from "@assistant-ui/core";
4
4
  import { useAuiState } from "@assistant-ui/store";
5
5
 
6
+ /**
7
+ * @deprecated Use {@link useAuiState} to select and narrow `s.part`.
8
+ * Return `null` for optional rendering, or throw inside the selector to
9
+ * preserve the old hook's strict behavior.
10
+ *
11
+ * @example
12
+ * ```tsx
13
+ * const image = useAuiState((s) => {
14
+ * if (s.part.type !== "image") return null;
15
+ * return s.part;
16
+ * });
17
+ * ```
18
+ *
19
+ * See the {@link https://assistant-ui.com/docs/migrations/v0-12 migration guide}.
20
+ */
6
21
  export const useMessagePartImage = () => {
7
22
  const image = useAuiState((s) => {
8
23
  if (s.part.type !== "image")
@@ -6,6 +6,21 @@ import type {
6
6
  } from "@assistant-ui/core";
7
7
  import { useAuiState } from "@assistant-ui/store";
8
8
 
9
+ /**
10
+ * @deprecated Use {@link useAuiState} to select and narrow `s.part`.
11
+ * Return `null` for optional rendering, or throw inside the selector to
12
+ * preserve the old hook's strict behavior.
13
+ *
14
+ * @example
15
+ * ```tsx
16
+ * const reasoning = useAuiState((s) => {
17
+ * if (s.part.type !== "reasoning") return null;
18
+ * return s.part;
19
+ * });
20
+ * ```
21
+ *
22
+ * See the {@link https://assistant-ui.com/docs/migrations/v0-12 migration guide}.
23
+ */
9
24
  export const useMessagePartReasoning = () => {
10
25
  const text = useAuiState((s) => {
11
26
  if (s.part.type !== "reasoning")
@@ -3,6 +3,21 @@
3
3
  import type { SourceMessagePart, MessagePartState } from "@assistant-ui/core";
4
4
  import { useAuiState } from "@assistant-ui/store";
5
5
 
6
+ /**
7
+ * @deprecated Use {@link useAuiState} to select and narrow `s.part`.
8
+ * Return `null` for optional rendering, or throw inside the selector to
9
+ * preserve the old hook's strict behavior.
10
+ *
11
+ * @example
12
+ * ```tsx
13
+ * const source = useAuiState((s) => {
14
+ * if (s.part.type !== "source") return null;
15
+ * return s.part;
16
+ * });
17
+ * ```
18
+ *
19
+ * See the {@link https://assistant-ui.com/docs/migrations/v0-12 migration guide}.
20
+ */
6
21
  export const useMessagePartSource = () => {
7
22
  const source = useAuiState((s) => {
8
23
  if (s.part.type !== "source")