@assistant-ui/core 0.2.0 → 0.2.3

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 (201) hide show
  1. package/README.md +45 -0
  2. package/dist/index.d.ts +2 -1
  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/model-context/tool.d.ts +25 -0
  7. package/dist/model-context/tool.d.ts.map +1 -1
  8. package/dist/model-context/tool.js +25 -0
  9. package/dist/model-context/tool.js.map +1 -1
  10. package/dist/react/AssistantRuntimeProvider.d.ts +33 -0
  11. package/dist/react/AssistantRuntimeProvider.d.ts.map +1 -1
  12. package/dist/react/AssistantRuntimeProvider.js +22 -0
  13. package/dist/react/AssistantRuntimeProvider.js.map +1 -1
  14. package/dist/react/client/DataRenderers.d.ts +7 -0
  15. package/dist/react/client/DataRenderers.d.ts.map +1 -1
  16. package/dist/react/client/DataRenderers.js +7 -0
  17. package/dist/react/client/DataRenderers.js.map +1 -1
  18. package/dist/react/client/Tools.d.ts +18 -1
  19. package/dist/react/client/Tools.d.ts.map +1 -1
  20. package/dist/react/client/Tools.js +24 -19
  21. package/dist/react/client/Tools.js.map +1 -1
  22. package/dist/react/index.d.ts +2 -1
  23. package/dist/react/index.d.ts.map +1 -1
  24. package/dist/react/index.js +1 -0
  25. package/dist/react/index.js.map +1 -1
  26. package/dist/react/model-context/makeAssistantDataUI.d.ts +13 -0
  27. package/dist/react/model-context/makeAssistantDataUI.d.ts.map +1 -1
  28. package/dist/react/model-context/makeAssistantDataUI.js +6 -0
  29. package/dist/react/model-context/makeAssistantDataUI.js.map +1 -1
  30. package/dist/react/model-context/makeAssistantTool.d.ts +15 -0
  31. package/dist/react/model-context/makeAssistantTool.d.ts.map +1 -1
  32. package/dist/react/model-context/makeAssistantTool.js +8 -0
  33. package/dist/react/model-context/makeAssistantTool.js.map +1 -1
  34. package/dist/react/model-context/makeAssistantToolUI.d.ts +15 -0
  35. package/dist/react/model-context/makeAssistantToolUI.d.ts.map +1 -1
  36. package/dist/react/model-context/makeAssistantToolUI.js +8 -0
  37. package/dist/react/model-context/makeAssistantToolUI.js.map +1 -1
  38. package/dist/react/model-context/toolbox.d.ts +29 -0
  39. package/dist/react/model-context/toolbox.d.ts.map +1 -1
  40. package/dist/react/model-context/useAssistantDataUI.d.ts +9 -0
  41. package/dist/react/model-context/useAssistantDataUI.d.ts.map +1 -1
  42. package/dist/react/model-context/useAssistantDataUI.js +6 -0
  43. package/dist/react/model-context/useAssistantDataUI.js.map +1 -1
  44. package/dist/react/model-context/useAssistantTool.d.ts +34 -0
  45. package/dist/react/model-context/useAssistantTool.d.ts.map +1 -1
  46. package/dist/react/model-context/useAssistantTool.js +30 -0
  47. package/dist/react/model-context/useAssistantTool.js.map +1 -1
  48. package/dist/react/model-context/useAssistantToolUI.d.ts +12 -0
  49. package/dist/react/model-context/useAssistantToolUI.d.ts.map +1 -1
  50. package/dist/react/model-context/useAssistantToolUI.js +9 -0
  51. package/dist/react/model-context/useAssistantToolUI.js.map +1 -1
  52. package/dist/react/model-context/useToolArgsStatus.d.ts +29 -0
  53. package/dist/react/model-context/useToolArgsStatus.d.ts.map +1 -1
  54. package/dist/react/model-context/useToolArgsStatus.js +24 -0
  55. package/dist/react/model-context/useToolArgsStatus.js.map +1 -1
  56. package/dist/react/primitive-hooks/useActionBarCopy.d.ts.map +1 -1
  57. package/dist/react/primitive-hooks/useActionBarCopy.js +4 -3
  58. package/dist/react/primitive-hooks/useActionBarCopy.js.map +1 -1
  59. package/dist/react/primitive-hooks/useComposerSend.d.ts.map +1 -1
  60. package/dist/react/primitive-hooks/useComposerSend.js +2 -3
  61. package/dist/react/primitive-hooks/useComposerSend.js.map +1 -1
  62. package/dist/react/primitives/message/MessageAttachments.js +1 -1
  63. package/dist/react/primitives/message/MessageAttachments.js.map +1 -1
  64. package/dist/react/primitives/message/MessageParts.d.ts.map +1 -1
  65. package/dist/react/primitives/message/MessageParts.js +14 -10
  66. package/dist/react/primitives/message/MessageParts.js.map +1 -1
  67. package/dist/react/primitives/messagePart/MessagePartInProgress.d.ts +6 -0
  68. package/dist/react/primitives/messagePart/MessagePartInProgress.d.ts.map +1 -0
  69. package/dist/react/primitives/messagePart/MessagePartInProgress.js +7 -0
  70. package/dist/react/primitives/messagePart/MessagePartInProgress.js.map +1 -0
  71. package/dist/react/runtimes/RemoteThreadListHookInstanceManager.d.ts +2 -0
  72. package/dist/react/runtimes/RemoteThreadListHookInstanceManager.d.ts.map +1 -1
  73. package/dist/react/runtimes/RemoteThreadListThreadListRuntimeCore.d.ts +2 -0
  74. package/dist/react/runtimes/RemoteThreadListThreadListRuntimeCore.d.ts.map +1 -1
  75. package/dist/react/runtimes/cloud/auiV0.d.ts +10 -1
  76. package/dist/react/runtimes/cloud/auiV0.d.ts.map +1 -1
  77. package/dist/react/runtimes/cloud/auiV0.js +21 -3
  78. package/dist/react/runtimes/cloud/auiV0.js.map +1 -1
  79. package/dist/react/runtimes/useToolInvocations.d.ts +11 -1
  80. package/dist/react/runtimes/useToolInvocations.d.ts.map +1 -1
  81. package/dist/react/runtimes/useToolInvocations.js +325 -256
  82. package/dist/react/runtimes/useToolInvocations.js.map +1 -1
  83. package/dist/react/types/MessagePartComponentTypes.d.ts +11 -0
  84. package/dist/react/types/MessagePartComponentTypes.d.ts.map +1 -1
  85. package/dist/react/types/scopes/tools.d.ts +4 -0
  86. package/dist/react/types/scopes/tools.d.ts.map +1 -1
  87. package/dist/runtime/api/composer-runtime.d.ts +1 -0
  88. package/dist/runtime/api/composer-runtime.d.ts.map +1 -1
  89. package/dist/runtime/api/composer-runtime.js +2 -0
  90. package/dist/runtime/api/composer-runtime.js.map +1 -1
  91. package/dist/runtime/api/thread-runtime.d.ts +2 -0
  92. package/dist/runtime/api/thread-runtime.d.ts.map +1 -1
  93. package/dist/runtime/base/base-composer-runtime-core.d.ts +1 -0
  94. package/dist/runtime/base/base-composer-runtime-core.d.ts.map +1 -1
  95. package/dist/runtime/base/base-composer-runtime-core.js +1 -1
  96. package/dist/runtime/base/base-composer-runtime-core.js.map +1 -1
  97. package/dist/runtime/base/base-thread-runtime-core.d.ts +1 -0
  98. package/dist/runtime/base/base-thread-runtime-core.d.ts.map +1 -1
  99. package/dist/runtime/base/base-thread-runtime-core.js.map +1 -1
  100. package/dist/runtime/base/default-edit-composer-runtime-core.d.ts +1 -0
  101. package/dist/runtime/base/default-edit-composer-runtime-core.d.ts.map +1 -1
  102. package/dist/runtime/base/default-edit-composer-runtime-core.js +3 -0
  103. package/dist/runtime/base/default-edit-composer-runtime-core.js.map +1 -1
  104. package/dist/runtime/base/default-thread-composer-runtime-core.d.ts +1 -0
  105. package/dist/runtime/base/default-thread-composer-runtime-core.d.ts.map +1 -1
  106. package/dist/runtime/base/default-thread-composer-runtime-core.js +12 -1
  107. package/dist/runtime/base/default-thread-composer-runtime-core.js.map +1 -1
  108. package/dist/runtime/interfaces/composer-runtime-core.d.ts +1 -0
  109. package/dist/runtime/interfaces/composer-runtime-core.d.ts.map +1 -1
  110. package/dist/runtime/interfaces/thread-runtime-core.d.ts +6 -0
  111. package/dist/runtime/interfaces/thread-runtime-core.d.ts.map +1 -1
  112. package/dist/runtimes/external-store/external-store-adapter.d.ts +15 -0
  113. package/dist/runtimes/external-store/external-store-adapter.d.ts.map +1 -1
  114. package/dist/runtimes/external-store/external-store-thread-list-runtime-core.d.ts +1 -1
  115. package/dist/runtimes/external-store/external-store-thread-list-runtime-core.d.ts.map +1 -1
  116. package/dist/runtimes/external-store/external-store-thread-list-runtime-core.js +14 -12
  117. package/dist/runtimes/external-store/external-store-thread-list-runtime-core.js.map +1 -1
  118. package/dist/runtimes/external-store/external-store-thread-runtime-core.d.ts +2 -0
  119. package/dist/runtimes/external-store/external-store-thread-runtime-core.d.ts.map +1 -1
  120. package/dist/runtimes/external-store/external-store-thread-runtime-core.js +13 -0
  121. package/dist/runtimes/external-store/external-store-thread-runtime-core.js.map +1 -1
  122. package/dist/runtimes/local/local-thread-runtime-core.d.ts +1 -0
  123. package/dist/runtimes/local/local-thread-runtime-core.d.ts.map +1 -1
  124. package/dist/runtimes/local/local-thread-runtime-core.js +1 -0
  125. package/dist/runtimes/local/local-thread-runtime-core.js.map +1 -1
  126. package/dist/runtimes/readonly/ReadonlyThreadRuntimeCore.d.ts +2 -0
  127. package/dist/runtimes/readonly/ReadonlyThreadRuntimeCore.d.ts.map +1 -1
  128. package/dist/runtimes/readonly/ReadonlyThreadRuntimeCore.js +2 -0
  129. package/dist/runtimes/readonly/ReadonlyThreadRuntimeCore.js.map +1 -1
  130. package/dist/runtimes/remote-thread-list/empty-thread-core.d.ts.map +1 -1
  131. package/dist/runtimes/remote-thread-list/empty-thread-core.js +2 -0
  132. package/dist/runtimes/remote-thread-list/empty-thread-core.js.map +1 -1
  133. package/dist/store/clients/model-context-client.d.ts.map +1 -1
  134. package/dist/store/clients/model-context-client.js +24 -4
  135. package/dist/store/clients/model-context-client.js.map +1 -1
  136. package/dist/store/clients/no-op-composer-client.d.ts.map +1 -1
  137. package/dist/store/clients/no-op-composer-client.js +1 -0
  138. package/dist/store/clients/no-op-composer-client.js.map +1 -1
  139. package/dist/store/runtime-clients/composer-runtime-client.d.ts.map +1 -1
  140. package/dist/store/runtime-clients/composer-runtime-client.js +1 -0
  141. package/dist/store/runtime-clients/composer-runtime-client.js.map +1 -1
  142. package/dist/store/scopes/composer.d.ts +9 -0
  143. package/dist/store/scopes/composer.d.ts.map +1 -1
  144. package/dist/store/scopes/model-context.d.ts +4 -1
  145. package/dist/store/scopes/model-context.d.ts.map +1 -1
  146. package/dist/types/index.d.ts +1 -1
  147. package/dist/types/index.d.ts.map +1 -1
  148. package/dist/types/message.d.ts +50 -1
  149. package/dist/types/message.d.ts.map +1 -1
  150. package/dist/types/message.js +2 -1
  151. package/dist/types/message.js.map +1 -1
  152. package/package.json +7 -7
  153. package/src/index.ts +6 -0
  154. package/src/model-context/tool.ts +25 -0
  155. package/src/react/AssistantRuntimeProvider.tsx +33 -0
  156. package/src/react/client/DataRenderers.ts +7 -0
  157. package/src/react/client/Tools.ts +56 -22
  158. package/src/react/index.ts +2 -1
  159. package/src/react/model-context/makeAssistantDataUI.ts +13 -0
  160. package/src/react/model-context/makeAssistantTool.ts +15 -0
  161. package/src/react/model-context/makeAssistantToolUI.ts +15 -0
  162. package/src/react/model-context/toolbox.ts +32 -1
  163. package/src/react/model-context/useAssistantDataUI.ts +9 -0
  164. package/src/react/model-context/useAssistantTool.ts +34 -0
  165. package/src/react/model-context/useAssistantToolUI.ts +12 -0
  166. package/src/react/model-context/useToolArgsStatus.ts +29 -0
  167. package/src/react/primitive-hooks/useActionBarCopy.ts +9 -5
  168. package/src/react/primitive-hooks/useComposerSend.ts +2 -3
  169. package/src/react/primitives/message/MessageAttachments.test.tsx +50 -0
  170. package/src/react/primitives/message/MessageAttachments.tsx +1 -1
  171. package/src/react/primitives/message/MessageParts.tsx +20 -9
  172. package/src/react/primitives/messagePart/MessagePartInProgress.ts +15 -0
  173. package/src/react/runtimes/cloud/auiV0.ts +37 -4
  174. package/src/react/runtimes/useToolInvocations.ts +422 -333
  175. package/src/react/types/MessagePartComponentTypes.ts +11 -0
  176. package/src/react/types/scopes/tools.ts +5 -0
  177. package/src/runtime/api/composer-runtime.ts +3 -0
  178. package/src/runtime/base/base-composer-runtime-core.ts +2 -1
  179. package/src/runtime/base/base-thread-runtime-core.ts +1 -0
  180. package/src/runtime/base/default-edit-composer-runtime-core.ts +4 -0
  181. package/src/runtime/base/default-thread-composer-runtime-core.ts +12 -1
  182. package/src/runtime/interfaces/composer-runtime-core.ts +1 -0
  183. package/src/runtime/interfaces/thread-runtime-core.ts +6 -0
  184. package/src/runtimes/external-store/external-store-adapter.ts +15 -0
  185. package/src/runtimes/external-store/external-store-thread-list-runtime-core.ts +15 -9
  186. package/src/runtimes/external-store/external-store-thread-runtime-core.ts +13 -0
  187. package/src/runtimes/local/local-thread-runtime-core.ts +1 -0
  188. package/src/runtimes/readonly/ReadonlyThreadRuntimeCore.ts +2 -0
  189. package/src/runtimes/remote-thread-list/empty-thread-core.ts +2 -0
  190. package/src/store/clients/model-context-client.test.ts +108 -0
  191. package/src/store/clients/model-context-client.ts +36 -6
  192. package/src/store/clients/no-op-composer-client.ts +1 -0
  193. package/src/store/runtime-clients/composer-runtime-client.ts +1 -0
  194. package/src/store/scopes/composer.ts +9 -0
  195. package/src/store/scopes/model-context.ts +4 -1
  196. package/src/tests/auiV0Encode.test.ts +55 -0
  197. package/src/tests/composer-can-send.test.ts +112 -0
  198. package/src/tests/external-store-thread-list-runtime-core.test.ts +34 -0
  199. package/src/tests/external-store-thread-runtime-core.test.ts +113 -0
  200. package/src/types/index.ts +2 -0
  201. package/src/types/message.ts +66 -7
@@ -2,11 +2,20 @@ import { useEffect } from "react";
2
2
  import { useAui } from "@assistant-ui/store";
3
3
  import type { DataMessagePartComponent } from "../types/MessagePartComponentTypes";
4
4
 
5
+ /** Props used to register a renderer for `data` message parts. */
5
6
  export type AssistantDataUIProps<T = any> = {
7
+ /** Data part name this renderer handles. */
6
8
  name: string;
9
+ /** Component rendered for matching data message parts. */
7
10
  render: DataMessagePartComponent<T>;
8
11
  };
9
12
 
13
+ /**
14
+ * Registers a renderer for named `data` message parts while the component is
15
+ * mounted.
16
+ *
17
+ * @param dataUI - Data renderer registration, or `null` to skip registration.
18
+ */
10
19
  export const useAssistantDataUI = (dataUI: AssistantDataUIProps | null) => {
11
20
  const aui = useAui();
12
21
  useEffect(() => {
@@ -3,13 +3,47 @@ import { useAui } from "@assistant-ui/store";
3
3
  import type { ToolCallMessagePartComponent } from "../types/MessagePartComponentTypes";
4
4
  import type { AssistantToolProps as CoreAssistantToolProps } from "../..";
5
5
 
6
+ /**
7
+ * Props used to register a tool from React.
8
+ */
6
9
  export type AssistantToolProps<
7
10
  TArgs extends Record<string, unknown>,
8
11
  TResult,
9
12
  > = CoreAssistantToolProps<TArgs, TResult> & {
13
+ /** Component used to render calls to this tool in assistant messages. */
10
14
  render?: ToolCallMessagePartComponent<TArgs, TResult> | undefined;
11
15
  };
12
16
 
17
+ /**
18
+ * Registers a tool with the assistant model context while the component is
19
+ * mounted.
20
+ *
21
+ * If `render` is provided, it is also installed as the renderer for matching
22
+ * tool-call message parts. The registration is removed automatically when the
23
+ * component unmounts or the tool definition changes.
24
+ *
25
+ * Pass a referentially stable tool object, such as one declared at module
26
+ * scope or memoized with `useMemo`, to avoid re-registering on every render.
27
+ *
28
+ * @param tool - Tool definition and name to register.
29
+ *
30
+ * @example
31
+ * ```tsx
32
+ * const weatherTool = {
33
+ * toolName: "get_weather",
34
+ * type: "frontend",
35
+ * description: "Get the weather for a city.",
36
+ * parameters: weatherSchema,
37
+ * execute: async ({ city }: { city: string }) => fetchWeather(city),
38
+ * render: WeatherToolUI,
39
+ * } satisfies AssistantToolProps<{ city: string }, Weather>;
40
+ *
41
+ * function WeatherToolRegistration() {
42
+ * useAssistantTool(weatherTool);
43
+ * return null;
44
+ * }
45
+ * ```
46
+ */
13
47
  export const useAssistantTool = <
14
48
  TArgs extends Record<string, unknown>,
15
49
  TResult,
@@ -2,11 +2,23 @@ import { useEffect } from "react";
2
2
  import { useAui } from "@assistant-ui/store";
3
3
  import type { ToolCallMessagePartComponent } from "../types/MessagePartComponentTypes";
4
4
 
5
+ /** Props used to register a renderer for tool-call message parts. */
5
6
  export type AssistantToolUIProps<TArgs, TResult> = {
7
+ /** Name of the tool whose calls should use this renderer. */
6
8
  toolName: string;
9
+ /** Component rendered for matching tool-call message parts. */
7
10
  render: ToolCallMessagePartComponent<TArgs, TResult>;
8
11
  };
9
12
 
13
+ /**
14
+ * Registers a tool-call renderer while the component is mounted.
15
+ *
16
+ * This only affects rendering. Pair it with {@link useAssistantTool},
17
+ * {@link Tools}, or a backend tool registry to expose the actual tool
18
+ * definition to the model.
19
+ *
20
+ * @param tool - Tool renderer registration, or `null` to skip registration.
21
+ */
10
22
  export const useAssistantToolUI = (
11
23
  tool: AssistantToolUIProps<any, any> | null,
12
24
  ) => {
@@ -7,13 +7,42 @@ import {
7
7
 
8
8
  type PropFieldStatus = "streaming" | "complete";
9
9
 
10
+ /**
11
+ * Streaming completion status for the arguments of the current tool call.
12
+ */
10
13
  export type ToolArgsStatus<
11
14
  TArgs extends Record<string, unknown> = Record<string, unknown>,
12
15
  > = {
16
+ /** Overall lifecycle state of the tool-call part. */
13
17
  status: "running" | "complete" | "incomplete" | "requires-action";
18
+ /** Per-argument status keyed by argument name. */
14
19
  propStatus: Partial<Record<keyof TArgs, PropFieldStatus>>;
15
20
  };
16
21
 
22
+ /**
23
+ * Reads whether each argument field for the current tool-call message part is
24
+ * still streaming or complete.
25
+ *
26
+ * Use inside a tool-call renderer to avoid showing incomplete argument values
27
+ * as final.
28
+ *
29
+ * @throws If called outside a tool-call message part.
30
+ *
31
+ * @example
32
+ * ```tsx
33
+ * function WeatherToolUI({
34
+ * args,
35
+ * }: ToolCallMessagePartProps<{ city: string }>) {
36
+ * const { propStatus } = useToolArgsStatus<{ city: string }>();
37
+ *
38
+ * return (
39
+ * <span>
40
+ * {propStatus.city === "streaming" ? "Reading city..." : args.city}
41
+ * </span>
42
+ * );
43
+ * }
44
+ * ```
45
+ */
17
46
  export const useToolArgsStatus = <
18
47
  TArgs extends Record<string, unknown> = Record<string, unknown>,
19
48
  >(): ToolArgsStatus<TArgs> => {
@@ -27,11 +27,15 @@ export const useActionBarCopy = ({
27
27
  if (!valueToCopy) return;
28
28
 
29
29
  const write = copyToClipboard ?? (() => {});
30
- const result = write(valueToCopy);
31
- Promise.resolve(result).then(() => {
32
- aui.message().setIsCopied(true);
33
- setTimeout(() => aui.message().setIsCopied(false), copiedDuration);
34
- });
30
+ // The rejection handler swallows clipboard write failures (permission denied,
31
+ // API unavailable) so they don't surface as unhandled promise rejections.
32
+ Promise.resolve(write(valueToCopy)).then(
33
+ () => {
34
+ aui.message().setIsCopied(true);
35
+ setTimeout(() => aui.message().setIsCopied(false), copiedDuration);
36
+ },
37
+ () => {},
38
+ );
35
39
  }, [aui, isEditing, composerValue, copiedDuration, copyToClipboard]);
36
40
 
37
41
  return { copy, disabled, isCopied };
@@ -6,9 +6,8 @@ export const useComposerSend = () => {
6
6
  const aui = useAui();
7
7
  const disabled = useAuiState(
8
8
  (s) =>
9
- (s.thread.isRunning && !s.thread.capabilities.queue) ||
10
- !s.composer.isEditing ||
11
- s.composer.isEmpty,
9
+ !s.composer.canSend ||
10
+ (s.thread.isRunning && !s.thread.capabilities.queue),
12
11
  );
13
12
 
14
13
  const send = useCallback(
@@ -0,0 +1,50 @@
1
+ import { describe, expect, it, vi } from "vitest";
2
+ import type { ReactElement } from "react";
3
+ import { MessagePrimitiveAttachments } from "./MessageAttachments";
4
+
5
+ const mockUseAuiState = vi.fn();
6
+ type UseAuiStateSelector = Parameters<
7
+ typeof import("@assistant-ui/store")["useAuiState"]
8
+ >[0];
9
+ type AttachmentsElement = ReactElement<{ children: () => null }>;
10
+
11
+ vi.mock("react", async (importOriginal) => {
12
+ const actual = await importOriginal<typeof import("react")>();
13
+ return {
14
+ ...actual,
15
+ useMemo: (factory: () => unknown) => factory(),
16
+ };
17
+ });
18
+
19
+ vi.mock("@assistant-ui/store", async (importOriginal) => {
20
+ const actual = await importOriginal<typeof import("@assistant-ui/store")>();
21
+ return {
22
+ ...actual,
23
+ useAuiState: (selector: UseAuiStateSelector) => mockUseAuiState(selector),
24
+ };
25
+ });
26
+
27
+ const renderAttachmentsInner = () => {
28
+ const element = MessagePrimitiveAttachments({
29
+ children: () => null,
30
+ }) as AttachmentsElement;
31
+
32
+ const Inner = element.type as (props: typeof element.props) => unknown;
33
+ return Inner(element.props);
34
+ };
35
+
36
+ describe("MessagePrimitiveAttachments", () => {
37
+ it("treats missing user message attachments as empty", () => {
38
+ mockUseAuiState.mockImplementation((selector: UseAuiStateSelector) =>
39
+ selector({
40
+ message: {
41
+ role: "user",
42
+ attachments: undefined,
43
+ },
44
+ } as never),
45
+ );
46
+
47
+ expect(() => renderAttachmentsInner()).not.toThrow();
48
+ expect(renderAttachmentsInner()).toEqual([]);
49
+ });
50
+ });
@@ -93,7 +93,7 @@ const MessagePrimitiveAttachmentsInner: FC<{
93
93
  }> = ({ children }) => {
94
94
  const attachmentsCount = useAuiState((s) => {
95
95
  if (s.message.role !== "user") return 0;
96
- return s.message.attachments.length;
96
+ return (s.message.attachments ?? []).length;
97
97
  });
98
98
 
99
99
  return useMemo(
@@ -31,8 +31,9 @@ import type {
31
31
  ReasoningGroupComponent,
32
32
  QuoteMessagePartComponent,
33
33
  } from "../../types/MessagePartComponentTypes";
34
- import type { MessagePartStatus } from "../../../types/message";
34
+ import { isMcpAppUri, type MessagePartStatus } from "../../../types/message";
35
35
  import type { DataRenderersState } from "../../types/scopes/dataRenderers";
36
+ import type { ToolsState } from "../../types/scopes/tools";
36
37
  import { useShallow } from "zustand/shallow";
37
38
 
38
39
  type MessagePartRange =
@@ -511,6 +512,19 @@ const QuoteRendererImpl: FC<{ Quote: QuoteMessagePartComponent }> = ({
511
512
 
512
513
  const QuoteRenderer = memo(QuoteRendererImpl);
513
514
 
515
+ function resolveToolRender(
516
+ toolsState: ToolsState,
517
+ part: Extract<PartState, { type: "tool-call" }>,
518
+ ): ToolCallMessagePartComponent | null {
519
+ const entry = toolsState.tools[part.toolName];
520
+ const named = Array.isArray(entry) ? (entry[0] ?? null) : (entry ?? null);
521
+ if (named) return named;
522
+ if (isMcpAppUri(part.mcp?.app?.resourceUri) && toolsState.mcpApp) {
523
+ return toolsState.mcpApp.render;
524
+ }
525
+ return null;
526
+ }
527
+
514
528
  /**
515
529
  * Stable propless component that renders the registered tool UI for the
516
530
  * current part context. Reads tool registry and part state from context.
@@ -518,12 +532,9 @@ const QuoteRenderer = memo(QuoteRendererImpl);
518
532
  const RegisteredToolUI: FC = () => {
519
533
  const aui = useAui();
520
534
  const part = useAuiState((s) => s.part);
521
- const Render = useAuiState((s) => {
522
- if (s.part.type !== "tool-call") return null;
523
- const entry = s.tools.tools[s.part.toolName];
524
- if (Array.isArray(entry)) return entry[0] ?? null;
525
- return entry ?? null;
526
- });
535
+ const Render = useAuiState((s) =>
536
+ s.part.type === "tool-call" ? resolveToolRender(s.tools, s.part) : null,
537
+ );
527
538
 
528
539
  if (!Render || part.type !== "tool-call") return null;
529
540
 
@@ -638,8 +649,8 @@ export const MessagePartChildren: FC<{
638
649
  get part() {
639
650
  const state = getItem();
640
651
  if (state.type === "tool-call") {
641
- const entry = aui.tools().getState().tools[state.toolName];
642
- const hasUI = Array.isArray(entry) ? !!entry[0] : !!entry;
652
+ const toolsState = aui.tools().getState();
653
+ const hasUI = resolveToolRender(toolsState, state) !== null;
643
654
  const partMethods = aui.message().part({ index });
644
655
  return {
645
656
  ...state,
@@ -0,0 +1,15 @@
1
+ import type { FC, PropsWithChildren } from "react";
2
+ import { useAuiState } from "@assistant-ui/store";
3
+
4
+ export namespace MessagePartPrimitiveInProgress {
5
+ export type Props = PropsWithChildren;
6
+ }
7
+
8
+ export const MessagePartPrimitiveInProgress: FC<
9
+ MessagePartPrimitiveInProgress.Props
10
+ > = ({ children }) => {
11
+ const isInProgress = useAuiState((s) => s.part.status.type === "running");
12
+ return isInProgress ? children : null;
13
+ };
14
+
15
+ MessagePartPrimitiveInProgress.displayName = "MessagePartPrimitive.InProgress";
@@ -1,4 +1,8 @@
1
- import type { MessageStatus, ThreadMessage } from "../../../types/message";
1
+ import type {
2
+ MessageStatus,
3
+ SourceProviderMetadata,
4
+ ThreadMessage,
5
+ } from "../../../types/message";
2
6
  import { fromThreadMessageLike } from "../../../runtime/utils/thread-message-like";
3
7
  import type { CloudMessage } from "assistant-cloud";
4
8
  import { isJSONValue } from "../../../utils/json/is-json";
@@ -23,6 +27,16 @@ type AuiV0MessagePart =
23
27
  readonly id: string;
24
28
  readonly url: string;
25
29
  readonly title?: string;
30
+ readonly providerMetadata?: SourceProviderMetadata;
31
+ }
32
+ | {
33
+ readonly type: "source";
34
+ readonly sourceType: "document";
35
+ readonly id: string;
36
+ readonly title: string;
37
+ readonly mediaType: string;
38
+ readonly filename?: string;
39
+ readonly providerMetadata?: SourceProviderMetadata;
26
40
  }
27
41
  | {
28
42
  readonly type: "tool-call";
@@ -90,12 +104,31 @@ export function auiV0Encode(message: ThreadMessage): AuiV0Message {
90
104
  return { type: "reasoning", text: part.text };
91
105
 
92
106
  case "source":
107
+ if (part.sourceType === "url") {
108
+ return {
109
+ type: "source",
110
+ sourceType: "url",
111
+ id: part.id,
112
+ url: part.url,
113
+ ...(part.title != null ? { title: part.title } : undefined),
114
+ ...(part.providerMetadata != null
115
+ ? { providerMetadata: part.providerMetadata }
116
+ : undefined),
117
+ };
118
+ }
119
+
93
120
  return {
94
121
  type: "source",
95
- sourceType: part.sourceType,
122
+ sourceType: "document",
96
123
  id: part.id,
97
- url: part.url,
98
- ...(part.title ? { title: part.title } : undefined),
124
+ title: part.title,
125
+ mediaType: part.mediaType,
126
+ ...(part.filename != null
127
+ ? { filename: part.filename }
128
+ : undefined),
129
+ ...(part.providerMetadata != null
130
+ ? { providerMetadata: part.providerMetadata }
131
+ : undefined),
99
132
  };
100
133
 
101
134
  case "tool-call": {