@assistant-ui/react 0.14.5 → 0.14.7

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 (98) hide show
  1. package/LICENSE +21 -0
  2. package/dist/index.d.ts +2 -0
  3. package/dist/index.d.ts.map +1 -1
  4. package/dist/index.js +1 -0
  5. package/dist/index.js.map +1 -1
  6. package/dist/legacy-runtime/AssistantRuntimeProvider.d.ts +8 -2
  7. package/dist/legacy-runtime/AssistantRuntimeProvider.d.ts.map +1 -1
  8. package/dist/legacy-runtime/AssistantRuntimeProvider.js.map +1 -1
  9. package/dist/legacy-runtime/hooks/AssistantContext.d.ts +2 -2
  10. package/dist/legacy-runtime/hooks/AssistantContext.js +1 -1
  11. package/dist/legacy-runtime/hooks/AttachmentContext.d.ts +2 -2
  12. package/dist/legacy-runtime/hooks/AttachmentContext.js +1 -1
  13. package/dist/legacy-runtime/hooks/ComposerContext.d.ts +2 -2
  14. package/dist/legacy-runtime/hooks/ComposerContext.js +1 -1
  15. package/dist/legacy-runtime/hooks/MessageContext.d.ts +3 -3
  16. package/dist/legacy-runtime/hooks/MessageContext.js +2 -2
  17. package/dist/legacy-runtime/hooks/MessagePartContext.d.ts +2 -2
  18. package/dist/legacy-runtime/hooks/MessagePartContext.js +1 -1
  19. package/dist/legacy-runtime/hooks/ThreadContext.d.ts +4 -4
  20. package/dist/legacy-runtime/hooks/ThreadContext.js +2 -2
  21. package/dist/legacy-runtime/hooks/ThreadListItemContext.d.ts +2 -2
  22. package/dist/legacy-runtime/hooks/ThreadListItemContext.js +1 -1
  23. package/dist/mcp-apps/McpAppRenderer.d.ts +8 -0
  24. package/dist/mcp-apps/McpAppRenderer.d.ts.map +1 -1
  25. package/dist/mcp-apps/McpAppRenderer.js +8 -0
  26. package/dist/mcp-apps/McpAppRenderer.js.map +1 -1
  27. package/dist/mcp-apps/McpAppsRemoteHost.d.ts +7 -0
  28. package/dist/mcp-apps/McpAppsRemoteHost.d.ts.map +1 -1
  29. package/dist/mcp-apps/McpAppsRemoteHost.js +7 -0
  30. package/dist/mcp-apps/McpAppsRemoteHost.js.map +1 -1
  31. package/dist/mcp-apps/utils.d.ts +7 -0
  32. package/dist/mcp-apps/utils.d.ts.map +1 -1
  33. package/dist/mcp-apps/utils.js +7 -0
  34. package/dist/mcp-apps/utils.js.map +1 -1
  35. package/dist/primitives/actionBar/ActionBarCopy.d.ts.map +1 -1
  36. package/dist/primitives/actionBar/ActionBarCopy.js +6 -1
  37. package/dist/primitives/actionBar/ActionBarCopy.js.map +1 -1
  38. package/dist/primitives/messagePart/MessagePartInProgress.d.ts +1 -5
  39. package/dist/primitives/messagePart/MessagePartInProgress.d.ts.map +1 -1
  40. package/dist/primitives/messagePart/MessagePartInProgress.js +1 -7
  41. package/dist/primitives/messagePart/MessagePartInProgress.js.map +1 -1
  42. package/dist/primitives/messagePart/useMessagePartData.d.ts +16 -0
  43. package/dist/primitives/messagePart/useMessagePartData.d.ts.map +1 -1
  44. package/dist/primitives/messagePart/useMessagePartData.js +16 -0
  45. package/dist/primitives/messagePart/useMessagePartData.js.map +1 -1
  46. package/dist/primitives/messagePart/useMessagePartFile.d.ts +15 -0
  47. package/dist/primitives/messagePart/useMessagePartFile.d.ts.map +1 -1
  48. package/dist/primitives/messagePart/useMessagePartFile.js +15 -0
  49. package/dist/primitives/messagePart/useMessagePartFile.js.map +1 -1
  50. package/dist/primitives/messagePart/useMessagePartImage.d.ts +15 -0
  51. package/dist/primitives/messagePart/useMessagePartImage.d.ts.map +1 -1
  52. package/dist/primitives/messagePart/useMessagePartImage.js +15 -0
  53. package/dist/primitives/messagePart/useMessagePartImage.js.map +1 -1
  54. package/dist/primitives/messagePart/useMessagePartReasoning.d.ts +15 -0
  55. package/dist/primitives/messagePart/useMessagePartReasoning.d.ts.map +1 -1
  56. package/dist/primitives/messagePart/useMessagePartReasoning.js +15 -0
  57. package/dist/primitives/messagePart/useMessagePartReasoning.js.map +1 -1
  58. package/dist/primitives/messagePart/useMessagePartSource.d.ts +15 -0
  59. package/dist/primitives/messagePart/useMessagePartSource.d.ts.map +1 -1
  60. package/dist/primitives/messagePart/useMessagePartSource.js +15 -0
  61. package/dist/primitives/messagePart/useMessagePartSource.js.map +1 -1
  62. package/dist/primitives/messagePart/useMessagePartText.d.ts +15 -0
  63. package/dist/primitives/messagePart/useMessagePartText.d.ts.map +1 -1
  64. package/dist/primitives/messagePart/useMessagePartText.js +15 -0
  65. package/dist/primitives/messagePart/useMessagePartText.js.map +1 -1
  66. package/dist/primitives/thread/useThreadViewportAutoScroll.d.ts +1 -1
  67. package/dist/primitives/thread/useThreadViewportAutoScroll.d.ts.map +1 -1
  68. package/dist/primitives/thread/useThreadViewportAutoScroll.js +50 -27
  69. package/dist/primitives/thread/useThreadViewportAutoScroll.js.map +1 -1
  70. package/dist/utils/smooth/useSmooth.d.ts.map +1 -1
  71. package/dist/utils/smooth/useSmooth.js +27 -10
  72. package/dist/utils/smooth/useSmooth.js.map +1 -1
  73. package/package.json +14 -16
  74. package/src/index.ts +6 -0
  75. package/src/legacy-runtime/AssistantRuntimeProvider.tsx +8 -2
  76. package/src/legacy-runtime/hooks/AssistantContext.ts +2 -2
  77. package/src/legacy-runtime/hooks/AttachmentContext.ts +2 -2
  78. package/src/legacy-runtime/hooks/ComposerContext.ts +2 -2
  79. package/src/legacy-runtime/hooks/MessageContext.ts +3 -3
  80. package/src/legacy-runtime/hooks/MessagePartContext.ts +2 -2
  81. package/src/legacy-runtime/hooks/ThreadContext.ts +4 -4
  82. package/src/legacy-runtime/hooks/ThreadListItemContext.ts +2 -2
  83. package/src/legacy-runtime/runtime-cores/assistant-transport/useToolInvocations.test.ts +254 -0
  84. package/src/mcp-apps/McpAppRenderer.tsx +8 -0
  85. package/src/mcp-apps/McpAppsRemoteHost.ts +7 -0
  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
  98. package/src/utils/smooth/useSmooth.ts +28 -12
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@assistant-ui/react",
3
- "version": "0.14.5",
3
+ "version": "0.14.7",
4
4
  "description": "Open-source TypeScript/React library for building production-grade AI chat experiences",
5
5
  "keywords": [
6
6
  "radix-ui",
@@ -47,16 +47,9 @@
47
47
  "README.md"
48
48
  ],
49
49
  "sideEffects": false,
50
- "scripts": {
51
- "prepack": "cp ../../README.md /tmp/aui-readme-pack.md && mv /tmp/aui-readme-pack.md ./README.md",
52
- "postpack": "rm -f ./README.md && ln -s ../../README.md ./README.md",
53
- "build": "aui-build",
54
- "test": "vitest run",
55
- "test:watch": "vitest"
56
- },
57
50
  "dependencies": {
58
- "@assistant-ui/core": "^0.2.2",
59
- "@assistant-ui/store": "^0.2.10",
51
+ "@assistant-ui/core": "^0.2.4",
52
+ "@assistant-ui/store": "^0.2.11",
60
53
  "@assistant-ui/tap": "^0.5.11",
61
54
  "@radix-ui/primitive": "^1.1.3",
62
55
  "@radix-ui/react-compose-refs": "^1.1.2",
@@ -64,8 +57,8 @@
64
57
  "@radix-ui/react-primitive": "^2.1.4",
65
58
  "@radix-ui/react-use-callback-ref": "^1.1.1",
66
59
  "@radix-ui/react-use-escape-keydown": "^1.1.1",
67
- "assistant-cloud": "^0.1.27",
68
- "assistant-stream": "^0.3.14",
60
+ "assistant-cloud": "^0.1.28",
61
+ "assistant-stream": "^0.3.15",
69
62
  "nanoid": "^5.1.11",
70
63
  "radix-ui": "^1.4.3",
71
64
  "react-textarea-autosize": "^8.5.9",
@@ -88,7 +81,6 @@
88
81
  }
89
82
  },
90
83
  "devDependencies": {
91
- "@assistant-ui/x-buildutils": "workspace:*",
92
84
  "@testing-library/react": "^16.3.2",
93
85
  "@types/json-schema": "^7.0.15",
94
86
  "@types/node": "^25.6.0",
@@ -97,11 +89,12 @@
97
89
  "jsdom": "^29.1.1",
98
90
  "react": "^19.2.5",
99
91
  "react-dom": "^19.2.5",
100
- "vitest": "^4.1.5"
92
+ "vitest": "^4.1.5",
93
+ "@assistant-ui/x-buildutils": "0.0.8"
101
94
  },
102
95
  "publishConfig": {
103
96
  "access": "public",
104
- "provenance": false
97
+ "provenance": true
105
98
  },
106
99
  "homepage": "https://www.assistant-ui.com/",
107
100
  "repository": {
@@ -111,5 +104,10 @@
111
104
  },
112
105
  "bugs": {
113
106
  "url": "https://github.com/assistant-ui/assistant-ui/issues"
107
+ },
108
+ "scripts": {
109
+ "build": "aui-build",
110
+ "test": "vitest run",
111
+ "test:watch": "vitest"
114
112
  }
115
- }
113
+ }
package/src/index.ts CHANGED
@@ -378,6 +378,12 @@ export {
378
378
  type RegisteredTrigger as Unstable_RegisteredTrigger,
379
379
  type TriggerBehavior as Unstable_TriggerBehavior,
380
380
  } from "./primitives/composer/trigger";
381
+ export type {
382
+ Unstable_DirectiveFormatter,
383
+ Unstable_DirectiveSegment,
384
+ Unstable_TriggerItem,
385
+ } from "@assistant-ui/core";
386
+ export { unstable_defaultDirectiveFormatter } from "@assistant-ui/core";
381
387
 
382
388
  export type { Assistant } from "./augmentations";
383
389
 
@@ -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);
@@ -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")