@assistant-ui/react-ink 0.0.2 → 0.0.4

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 (228) hide show
  1. package/README.md +86 -0
  2. package/dist/context/AssistantContext.d.ts +1 -7
  3. package/dist/context/AssistantContext.d.ts.map +1 -1
  4. package/dist/context/AssistantContext.js +1 -15
  5. package/dist/context/AssistantContext.js.map +1 -1
  6. package/dist/context/index.d.ts +1 -1
  7. package/dist/context/index.d.ts.map +1 -1
  8. package/dist/context/index.js +1 -1
  9. package/dist/context/index.js.map +1 -1
  10. package/dist/index.d.ts +13 -14
  11. package/dist/index.d.ts.map +1 -1
  12. package/dist/index.js +13 -16
  13. package/dist/index.js.map +1 -1
  14. package/dist/primitives/actionBar/ActionBarCopy.d.ts +1 -1
  15. package/dist/primitives/actionBar/ActionBarCopy.d.ts.map +1 -1
  16. package/dist/primitives/actionBar/ActionBarCopy.js +1 -1
  17. package/dist/primitives/actionBar/ActionBarCopy.js.map +1 -1
  18. package/dist/primitives/actionBar/ActionBarEdit.js +1 -1
  19. package/dist/primitives/actionBar/ActionBarEdit.js.map +1 -1
  20. package/dist/primitives/actionBar/ActionBarFeedbackNegative.js +1 -1
  21. package/dist/primitives/actionBar/ActionBarFeedbackNegative.js.map +1 -1
  22. package/dist/primitives/actionBar/ActionBarFeedbackPositive.js +1 -1
  23. package/dist/primitives/actionBar/ActionBarFeedbackPositive.js.map +1 -1
  24. package/dist/primitives/actionBar/ActionBarReload.js +1 -1
  25. package/dist/primitives/actionBar/ActionBarReload.js.map +1 -1
  26. package/dist/primitives/actionBar/index.d.ts +5 -5
  27. package/dist/primitives/actionBar/index.d.ts.map +1 -1
  28. package/dist/primitives/actionBar/index.js +5 -5
  29. package/dist/primitives/actionBar/index.js.map +1 -1
  30. package/dist/primitives/attachment/index.d.ts +4 -4
  31. package/dist/primitives/attachment/index.d.ts.map +1 -1
  32. package/dist/primitives/attachment/index.js +4 -4
  33. package/dist/primitives/attachment/index.js.map +1 -1
  34. package/dist/primitives/branchPicker/index.d.ts +4 -4
  35. package/dist/primitives/branchPicker/index.d.ts.map +1 -1
  36. package/dist/primitives/branchPicker/index.js +4 -4
  37. package/dist/primitives/branchPicker/index.js.map +1 -1
  38. package/dist/primitives/chainOfThought/index.d.ts +3 -3
  39. package/dist/primitives/chainOfThought/index.d.ts.map +1 -1
  40. package/dist/primitives/chainOfThought/index.js +3 -3
  41. package/dist/primitives/chainOfThought/index.js.map +1 -1
  42. package/dist/primitives/composer/ComposerAddAttachment.d.ts.map +1 -1
  43. package/dist/primitives/composer/ComposerAddAttachment.js +3 -5
  44. package/dist/primitives/composer/ComposerAddAttachment.js.map +1 -1
  45. package/dist/primitives/composer/ComposerCancel.js +3 -3
  46. package/dist/primitives/composer/ComposerCancel.js.map +1 -1
  47. package/dist/primitives/composer/ComposerSend.js +3 -3
  48. package/dist/primitives/composer/ComposerSend.js.map +1 -1
  49. package/dist/primitives/composer/index.d.ts +7 -10
  50. package/dist/primitives/composer/index.d.ts.map +1 -1
  51. package/dist/primitives/composer/index.js +7 -10
  52. package/dist/primitives/composer/index.js.map +1 -1
  53. package/dist/primitives/message/MessageContent.d.ts.map +1 -1
  54. package/dist/primitives/message/MessageContent.js +9 -3
  55. package/dist/primitives/message/MessageContent.js.map +1 -1
  56. package/dist/primitives/message/index.d.ts +5 -5
  57. package/dist/primitives/message/index.d.ts.map +1 -1
  58. package/dist/primitives/message/index.js +5 -5
  59. package/dist/primitives/message/index.js.map +1 -1
  60. package/dist/primitives/suggestion/index.d.ts +3 -3
  61. package/dist/primitives/suggestion/index.d.ts.map +1 -1
  62. package/dist/primitives/suggestion/index.js +3 -3
  63. package/dist/primitives/suggestion/index.js.map +1 -1
  64. package/dist/primitives/thread/ThreadEmpty.js +1 -1
  65. package/dist/primitives/thread/ThreadEmpty.js.map +1 -1
  66. package/dist/primitives/thread/ThreadMessages.d.ts +23 -7
  67. package/dist/primitives/thread/ThreadMessages.d.ts.map +1 -1
  68. package/dist/primitives/thread/ThreadMessages.js +67 -13
  69. package/dist/primitives/thread/ThreadMessages.js.map +1 -1
  70. package/dist/primitives/thread/index.d.ts +7 -7
  71. package/dist/primitives/thread/index.d.ts.map +1 -1
  72. package/dist/primitives/thread/index.js +7 -7
  73. package/dist/primitives/thread/index.js.map +1 -1
  74. package/dist/primitives/threadList/index.d.ts +3 -3
  75. package/dist/primitives/threadList/index.d.ts.map +1 -1
  76. package/dist/primitives/threadList/index.js +3 -3
  77. package/dist/primitives/threadList/index.js.map +1 -1
  78. package/dist/primitives/threadListItem/index.d.ts +6 -6
  79. package/dist/primitives/threadListItem/index.d.ts.map +1 -1
  80. package/dist/primitives/threadListItem/index.js +6 -6
  81. package/dist/primitives/threadListItem/index.js.map +1 -1
  82. package/dist/primitives/toolCall/ToolFallback.d.ts +33 -0
  83. package/dist/primitives/toolCall/ToolFallback.d.ts.map +1 -0
  84. package/dist/primitives/toolCall/ToolFallback.js +103 -0
  85. package/dist/primitives/toolCall/ToolFallback.js.map +1 -0
  86. package/dist/primitives/toolCall/index.d.ts +2 -0
  87. package/dist/primitives/toolCall/index.d.ts.map +1 -0
  88. package/dist/primitives/toolCall/index.js +2 -0
  89. package/dist/primitives/toolCall/index.js.map +1 -0
  90. package/dist/runtimes/useLocalRuntime.d.ts +1 -17
  91. package/dist/runtimes/useLocalRuntime.d.ts.map +1 -1
  92. package/dist/runtimes/useLocalRuntime.js +1 -61
  93. package/dist/runtimes/useLocalRuntime.js.map +1 -1
  94. package/package.json +5 -3
  95. package/src/context/AssistantContext.tsx +4 -31
  96. package/src/context/index.ts +4 -1
  97. package/src/index.ts +13 -39
  98. package/src/primitives/actionBar/ActionBarCopy.tsx +1 -1
  99. package/src/primitives/actionBar/ActionBarEdit.tsx +1 -1
  100. package/src/primitives/actionBar/ActionBarFeedbackNegative.tsx +1 -1
  101. package/src/primitives/actionBar/ActionBarFeedbackPositive.tsx +1 -1
  102. package/src/primitives/actionBar/ActionBarReload.tsx +1 -1
  103. package/src/primitives/actionBar/index.ts +16 -7
  104. package/src/primitives/attachment/index.ts +14 -5
  105. package/src/primitives/branchPicker/index.ts +8 -8
  106. package/src/primitives/chainOfThought/index.ts +5 -5
  107. package/src/primitives/composer/ComposerAddAttachment.tsx +3 -5
  108. package/src/primitives/composer/ComposerCancel.tsx +3 -3
  109. package/src/primitives/composer/ComposerSend.tsx +3 -3
  110. package/src/primitives/composer/index.ts +19 -19
  111. package/src/primitives/message/MessageContent.test.tsx +83 -0
  112. package/src/primitives/message/MessageContent.tsx +21 -15
  113. package/src/primitives/message/index.ts +13 -7
  114. package/src/primitives/suggestion/index.ts +6 -6
  115. package/src/primitives/thread/ThreadEmpty.tsx +1 -1
  116. package/src/primitives/thread/ThreadMessages.tsx +122 -27
  117. package/src/primitives/thread/index.ts +17 -11
  118. package/src/primitives/threadList/index.ts +12 -3
  119. package/src/primitives/threadListItem/index.ts +11 -11
  120. package/src/primitives/toolCall/ToolFallback.test.tsx +40 -0
  121. package/src/primitives/toolCall/ToolFallback.tsx +227 -0
  122. package/src/primitives/toolCall/index.ts +5 -0
  123. package/src/runtimes/useLocalRuntime.ts +4 -105
  124. package/dist/adapters/LocalStorageThreadListAdapter.d.ts +0 -15
  125. package/dist/adapters/LocalStorageThreadListAdapter.d.ts.map +0 -1
  126. package/dist/adapters/LocalStorageThreadListAdapter.js +0 -154
  127. package/dist/adapters/LocalStorageThreadListAdapter.js.map +0 -1
  128. package/dist/adapters/TitleGenerationAdapter.d.ts +0 -6
  129. package/dist/adapters/TitleGenerationAdapter.d.ts.map +0 -1
  130. package/dist/adapters/TitleGenerationAdapter.js +0 -15
  131. package/dist/adapters/TitleGenerationAdapter.js.map +0 -1
  132. package/dist/adapters/index.d.ts +0 -3
  133. package/dist/adapters/index.d.ts.map +0 -1
  134. package/dist/adapters/index.js +0 -3
  135. package/dist/adapters/index.js.map +0 -1
  136. package/dist/primitive-hooks/index.d.ts +0 -15
  137. package/dist/primitive-hooks/index.d.ts.map +0 -1
  138. package/dist/primitive-hooks/index.js +0 -15
  139. package/dist/primitive-hooks/index.js.map +0 -1
  140. package/dist/primitive-hooks/useActionBarCopy.d.ts +0 -10
  141. package/dist/primitive-hooks/useActionBarCopy.d.ts.map +0 -1
  142. package/dist/primitive-hooks/useActionBarCopy.js +0 -26
  143. package/dist/primitive-hooks/useActionBarCopy.js.map +0 -1
  144. package/dist/primitive-hooks/useActionBarEdit.d.ts +0 -5
  145. package/dist/primitive-hooks/useActionBarEdit.d.ts.map +0 -1
  146. package/dist/primitive-hooks/useActionBarEdit.js +0 -11
  147. package/dist/primitive-hooks/useActionBarEdit.js.map +0 -1
  148. package/dist/primitive-hooks/useActionBarFeedback.d.ts +0 -9
  149. package/dist/primitive-hooks/useActionBarFeedback.d.ts.map +0 -1
  150. package/dist/primitive-hooks/useActionBarFeedback.js +0 -19
  151. package/dist/primitive-hooks/useActionBarFeedback.js.map +0 -1
  152. package/dist/primitive-hooks/useActionBarReload.d.ts +0 -5
  153. package/dist/primitive-hooks/useActionBarReload.d.ts.map +0 -1
  154. package/dist/primitive-hooks/useActionBarReload.js +0 -13
  155. package/dist/primitive-hooks/useActionBarReload.js.map +0 -1
  156. package/dist/primitive-hooks/useComposerAddAttachment.d.ts +0 -5
  157. package/dist/primitive-hooks/useComposerAddAttachment.d.ts.map +0 -1
  158. package/dist/primitive-hooks/useComposerAddAttachment.js +0 -10
  159. package/dist/primitive-hooks/useComposerAddAttachment.js.map +0 -1
  160. package/dist/primitive-hooks/useComposerCancel.d.ts +0 -5
  161. package/dist/primitive-hooks/useComposerCancel.d.ts.map +0 -1
  162. package/dist/primitive-hooks/useComposerCancel.js +0 -11
  163. package/dist/primitive-hooks/useComposerCancel.js.map +0 -1
  164. package/dist/primitive-hooks/useComposerSend.d.ts +0 -5
  165. package/dist/primitive-hooks/useComposerSend.d.ts.map +0 -1
  166. package/dist/primitive-hooks/useComposerSend.js +0 -11
  167. package/dist/primitive-hooks/useComposerSend.js.map +0 -1
  168. package/dist/primitive-hooks/useEditComposerCancel.d.ts +0 -4
  169. package/dist/primitive-hooks/useEditComposerCancel.d.ts.map +0 -1
  170. package/dist/primitive-hooks/useEditComposerCancel.js +0 -10
  171. package/dist/primitive-hooks/useEditComposerCancel.js.map +0 -1
  172. package/dist/primitive-hooks/useEditComposerSend.d.ts +0 -5
  173. package/dist/primitive-hooks/useEditComposerSend.d.ts.map +0 -1
  174. package/dist/primitive-hooks/useEditComposerSend.js +0 -11
  175. package/dist/primitive-hooks/useEditComposerSend.js.map +0 -1
  176. package/dist/primitive-hooks/useMessageBranching.d.ts +0 -7
  177. package/dist/primitive-hooks/useMessageBranching.d.ts.map +0 -1
  178. package/dist/primitive-hooks/useMessageBranching.js +0 -15
  179. package/dist/primitive-hooks/useMessageBranching.js.map +0 -1
  180. package/dist/primitive-hooks/useMessageReload.d.ts +0 -5
  181. package/dist/primitive-hooks/useMessageReload.d.ts.map +0 -1
  182. package/dist/primitive-hooks/useMessageReload.js +0 -11
  183. package/dist/primitive-hooks/useMessageReload.js.map +0 -1
  184. package/dist/primitive-hooks/useThreadIsEmpty.d.ts +0 -2
  185. package/dist/primitive-hooks/useThreadIsEmpty.d.ts.map +0 -1
  186. package/dist/primitive-hooks/useThreadIsEmpty.js +0 -5
  187. package/dist/primitive-hooks/useThreadIsEmpty.js.map +0 -1
  188. package/dist/primitive-hooks/useThreadIsRunning.d.ts +0 -2
  189. package/dist/primitive-hooks/useThreadIsRunning.d.ts.map +0 -1
  190. package/dist/primitive-hooks/useThreadIsRunning.js +0 -5
  191. package/dist/primitive-hooks/useThreadIsRunning.js.map +0 -1
  192. package/dist/primitive-hooks/useThreadMessages.d.ts +0 -3
  193. package/dist/primitive-hooks/useThreadMessages.d.ts.map +0 -1
  194. package/dist/primitive-hooks/useThreadMessages.js +0 -5
  195. package/dist/primitive-hooks/useThreadMessages.js.map +0 -1
  196. package/dist/primitives/composer/EditComposerCancel.d.ts +0 -7
  197. package/dist/primitives/composer/EditComposerCancel.d.ts.map +0 -1
  198. package/dist/primitives/composer/EditComposerCancel.js +0 -8
  199. package/dist/primitives/composer/EditComposerCancel.js.map +0 -1
  200. package/dist/primitives/composer/EditComposerInput.d.ts +0 -10
  201. package/dist/primitives/composer/EditComposerInput.d.ts.map +0 -1
  202. package/dist/primitives/composer/EditComposerInput.js +0 -19
  203. package/dist/primitives/composer/EditComposerInput.js.map +0 -1
  204. package/dist/primitives/composer/EditComposerSend.d.ts +0 -7
  205. package/dist/primitives/composer/EditComposerSend.d.ts.map +0 -1
  206. package/dist/primitives/composer/EditComposerSend.js +0 -8
  207. package/dist/primitives/composer/EditComposerSend.js.map +0 -1
  208. package/src/adapters/LocalStorageThreadListAdapter.tsx +0 -227
  209. package/src/adapters/TitleGenerationAdapter.ts +0 -20
  210. package/src/adapters/index.ts +0 -5
  211. package/src/primitive-hooks/index.ts +0 -20
  212. package/src/primitive-hooks/useActionBarCopy.ts +0 -38
  213. package/src/primitive-hooks/useActionBarEdit.ts +0 -13
  214. package/src/primitive-hooks/useActionBarFeedback.ts +0 -28
  215. package/src/primitive-hooks/useActionBarReload.ts +0 -18
  216. package/src/primitive-hooks/useComposerAddAttachment.ts +0 -16
  217. package/src/primitive-hooks/useComposerCancel.ts +0 -13
  218. package/src/primitive-hooks/useComposerSend.ts +0 -13
  219. package/src/primitive-hooks/useEditComposerCancel.ts +0 -12
  220. package/src/primitive-hooks/useEditComposerSend.ts +0 -13
  221. package/src/primitive-hooks/useMessageBranching.ts +0 -18
  222. package/src/primitive-hooks/useMessageReload.ts +0 -13
  223. package/src/primitive-hooks/useThreadIsEmpty.ts +0 -5
  224. package/src/primitive-hooks/useThreadIsRunning.ts +0 -5
  225. package/src/primitive-hooks/useThreadMessages.ts +0 -6
  226. package/src/primitives/composer/EditComposerCancel.tsx +0 -20
  227. package/src/primitives/composer/EditComposerInput.tsx +0 -40
  228. package/src/primitives/composer/EditComposerSend.tsx +0 -25
@@ -0,0 +1,83 @@
1
+ import type { ReactElement } from "react";
2
+ import { afterEach, describe, expect, it, vi } from "vitest";
3
+ import { cleanup, render } from "ink-testing-library";
4
+ import { MessageContent } from "./MessageContent";
5
+
6
+ const mockUseAui = vi.fn();
7
+ const mockUseAuiState = vi.fn();
8
+
9
+ type UseAuiStateSelector = Parameters<
10
+ typeof import("@assistant-ui/store")["useAuiState"]
11
+ >[0];
12
+
13
+ vi.mock("@assistant-ui/store", async (importOriginal) => {
14
+ const actual = await importOriginal<typeof import("@assistant-ui/store")>();
15
+ return {
16
+ ...actual,
17
+ useAui: () => mockUseAui(),
18
+ useAuiState: (selector: UseAuiStateSelector) => mockUseAuiState(selector),
19
+ };
20
+ });
21
+
22
+ const renderFrame = async (node: ReactElement) => {
23
+ const instance = render(node);
24
+ await new Promise((resolve) => setTimeout(resolve, 0));
25
+ return instance.lastFrame() ?? "";
26
+ };
27
+
28
+ afterEach(() => {
29
+ cleanup();
30
+ vi.clearAllMocks();
31
+ });
32
+
33
+ describe("MessageContent", () => {
34
+ it("renders incomplete tool calls as errors instead of running forever", async () => {
35
+ const state = {
36
+ tools: { tools: {} },
37
+ message: {
38
+ content: [
39
+ {
40
+ type: "tool-call",
41
+ toolCallId: "tool-call-1",
42
+ toolName: "search",
43
+ args: { query: "weather" },
44
+ argsText: '{"query":"weather"}',
45
+ },
46
+ ],
47
+ parts: [
48
+ {
49
+ type: "tool-call",
50
+ toolCallId: "tool-call-1",
51
+ toolName: "search",
52
+ args: { query: "weather" },
53
+ argsText: '{"query":"weather"}',
54
+ status: {
55
+ type: "incomplete",
56
+ reason: "error",
57
+ error: "request failed",
58
+ },
59
+ },
60
+ ],
61
+ },
62
+ };
63
+
64
+ mockUseAuiState.mockImplementation((selector: UseAuiStateSelector) =>
65
+ selector(state as never),
66
+ );
67
+ mockUseAui.mockReturnValue({
68
+ message: () => ({
69
+ part: () => ({
70
+ addToolResult: vi.fn(),
71
+ resumeToolCall: vi.fn(),
72
+ }),
73
+ }),
74
+ });
75
+
76
+ const frame = await renderFrame(<MessageContent />);
77
+
78
+ expect(frame).toContain("search");
79
+ expect(frame).toContain("Error:");
80
+ expect(frame).toContain("request failed");
81
+ expect(frame).not.toContain("Running...");
82
+ });
83
+ });
@@ -3,16 +3,17 @@ import { Text } from "ink";
3
3
  import type {
4
4
  ThreadUserMessagePart,
5
5
  ThreadAssistantMessagePart,
6
- ToolCallMessagePart,
7
- DataMessagePart,
6
+ MessagePartState,
8
7
  } from "@assistant-ui/core";
9
8
  import { useAui, useAuiState } from "@assistant-ui/store";
10
9
  import type {
11
10
  ToolCallMessagePartProps,
12
11
  DataMessagePartProps,
13
12
  } from "../../types";
13
+ import { ToolFallback } from "../toolCall/ToolFallback";
14
14
 
15
15
  type MessageContentPart = ThreadUserMessagePart | ThreadAssistantMessagePart;
16
+ type MessageContentStatePart = MessagePartState;
16
17
 
17
18
  export type MessageContentProps = {
18
19
  renderText?: (props: {
@@ -59,9 +60,12 @@ const ToolUIDisplay = ({
59
60
  index,
60
61
  }: {
61
62
  Fallback:
62
- | ((props: { part: ToolCallMessagePart; index: number }) => ReactElement)
63
+ | ((props: {
64
+ part: Extract<MessageContentPart, { type: "tool-call" }>;
65
+ index: number;
66
+ }) => ReactElement)
63
67
  | undefined;
64
- part: ToolCallMessagePart;
68
+ part: Extract<MessageContentStatePart, { type: "tool-call" }>;
65
69
  index: number;
66
70
  }) => {
67
71
  const aui = useAui();
@@ -75,18 +79,17 @@ const ToolUIDisplay = ({
75
79
  () => aui.message().part({ index }),
76
80
  [aui, index],
77
81
  );
82
+ const toolProps = {
83
+ ...(part as ToolCallMessagePartProps),
84
+ addResult: partMethods.addToolResult,
85
+ resume: partMethods.resumeToolCall,
86
+ };
78
87
 
79
88
  if (Render) {
80
- return (
81
- <Render
82
- {...(part as ToolCallMessagePartProps)}
83
- addResult={partMethods.addToolResult}
84
- resume={partMethods.resumeToolCall}
85
- />
86
- );
89
+ return <Render {...toolProps} />;
87
90
  }
88
91
  if (Fallback) return <Fallback part={part} index={index} />;
89
- return null;
92
+ return <ToolFallback {...toolProps} />;
90
93
  };
91
94
 
92
95
  const DataUIDisplay = ({
@@ -95,9 +98,12 @@ const DataUIDisplay = ({
95
98
  index,
96
99
  }: {
97
100
  Fallback:
98
- | ((props: { part: DataMessagePart; index: number }) => ReactElement)
101
+ | ((props: {
102
+ part: Extract<MessageContentPart, { type: "data" }>;
103
+ index: number;
104
+ }) => ReactElement)
99
105
  | undefined;
100
- part: DataMessagePart;
106
+ part: Extract<MessageContentStatePart, { type: "data" }>;
101
107
  index: number;
102
108
  }) => {
103
109
  const Render = useAuiState((s) => {
@@ -119,7 +125,7 @@ export const MessageContent = ({
119
125
  renderFile,
120
126
  renderData,
121
127
  }: MessageContentProps) => {
122
- const content = useAuiState((s) => s.message.content);
128
+ const content = useAuiState((s) => s.message.parts);
123
129
 
124
130
  return (
125
131
  <>
@@ -1,11 +1,17 @@
1
- export { MessageRoot, type MessageRootProps } from "./MessageRoot";
2
- export { MessageContent, type MessageContentProps } from "./MessageContent";
3
1
  export {
4
- MessagePrimitiveParts,
5
- MessagePrimitivePartByIndex,
2
+ MessageRoot as Root,
3
+ type MessageRootProps as RootProps,
4
+ } from "./MessageRoot";
5
+ export {
6
+ MessageContent as Content,
7
+ type MessageContentProps as ContentProps,
8
+ } from "./MessageContent";
9
+ export {
10
+ MessagePrimitiveParts as Parts,
11
+ MessagePrimitivePartByIndex as PartByIndex,
6
12
  } from "./MessageParts";
7
- export { MessageIf, type MessageIfProps } from "./MessageIf";
13
+ export { MessageIf as If, type MessageIfProps as IfProps } from "./MessageIf";
8
14
  export {
9
- MessageAttachments,
10
- MessageAttachmentByIndex,
15
+ MessageAttachments as Attachments,
16
+ MessageAttachmentByIndex as AttachmentByIndex,
11
17
  } from "./MessageAttachments";
@@ -1,12 +1,12 @@
1
1
  export {
2
- SuggestionTitle,
3
- type SuggestionTitleProps,
2
+ SuggestionTitle as Title,
3
+ type SuggestionTitleProps as TitleProps,
4
4
  } from "./SuggestionTitle";
5
5
  export {
6
- SuggestionDescription,
7
- type SuggestionDescriptionProps,
6
+ SuggestionDescription as Description,
7
+ type SuggestionDescriptionProps as DescriptionProps,
8
8
  } from "./SuggestionDescription";
9
9
  export {
10
- SuggestionTrigger,
11
- type SuggestionTriggerProps,
10
+ SuggestionTrigger as Trigger,
11
+ type SuggestionTriggerProps as TriggerProps,
12
12
  } from "./SuggestionTrigger";
@@ -1,5 +1,5 @@
1
1
  import type { ReactNode } from "react";
2
- import { useThreadIsEmpty } from "../../primitive-hooks/useThreadIsEmpty";
2
+ import { useThreadIsEmpty } from "@assistant-ui/core/react";
3
3
 
4
4
  export type ThreadEmptyProps = {
5
5
  children: ReactNode;
@@ -1,42 +1,137 @@
1
- import { type ReactElement } from "react";
1
+ import { type ComponentType, type FC, memo } from "react";
2
2
  import { Box } from "ink";
3
3
  import type { ThreadMessage } from "@assistant-ui/core";
4
- import { useAui, useAuiState, AuiProvider, Derived } from "@assistant-ui/store";
4
+ import { useAuiState } from "@assistant-ui/store";
5
+ import { MessageByIndexProvider } from "@assistant-ui/core/react";
6
+
7
+ type MessageComponents =
8
+ | {
9
+ Message: ComponentType;
10
+ EditComposer?: ComponentType | undefined;
11
+ UserEditComposer?: ComponentType | undefined;
12
+ AssistantEditComposer?: ComponentType | undefined;
13
+ SystemEditComposer?: ComponentType | undefined;
14
+ UserMessage?: ComponentType | undefined;
15
+ AssistantMessage?: ComponentType | undefined;
16
+ SystemMessage?: ComponentType | undefined;
17
+ }
18
+ | {
19
+ Message?: ComponentType | undefined;
20
+ EditComposer?: ComponentType | undefined;
21
+ UserEditComposer?: ComponentType | undefined;
22
+ AssistantEditComposer?: ComponentType | undefined;
23
+ SystemEditComposer?: ComponentType | undefined;
24
+ UserMessage: ComponentType;
25
+ AssistantMessage: ComponentType;
26
+ SystemMessage?: ComponentType | undefined;
27
+ };
5
28
 
6
29
  export type ThreadMessagesProps = {
7
- renderMessage: (props: {
8
- message: ThreadMessage;
9
- index: number;
10
- }) => ReactElement;
30
+ components: MessageComponents;
11
31
  };
12
32
 
13
- const MessageScope = ({
14
- index,
15
- children,
16
- }: {
17
- index: number;
18
- children: ReactElement;
33
+ const DEFAULT_SYSTEM_MESSAGE = () => null;
34
+
35
+ const getComponent = (
36
+ components: MessageComponents,
37
+ role: ThreadMessage["role"],
38
+ isEditing: boolean,
39
+ ) => {
40
+ switch (role) {
41
+ case "user":
42
+ if (isEditing) {
43
+ return (
44
+ components.UserEditComposer ??
45
+ components.EditComposer ??
46
+ components.UserMessage ??
47
+ (components.Message as ComponentType)
48
+ );
49
+ } else {
50
+ return components.UserMessage ?? (components.Message as ComponentType);
51
+ }
52
+ case "assistant":
53
+ if (isEditing) {
54
+ return (
55
+ components.AssistantEditComposer ??
56
+ components.EditComposer ??
57
+ components.AssistantMessage ??
58
+ (components.Message as ComponentType)
59
+ );
60
+ } else {
61
+ return (
62
+ components.AssistantMessage ?? (components.Message as ComponentType)
63
+ );
64
+ }
65
+ case "system":
66
+ if (isEditing) {
67
+ return (
68
+ components.SystemEditComposer ??
69
+ components.EditComposer ??
70
+ components.SystemMessage ??
71
+ (components.Message as ComponentType) ??
72
+ DEFAULT_SYSTEM_MESSAGE
73
+ );
74
+ } else {
75
+ return (
76
+ components.SystemMessage ??
77
+ (components.Message as ComponentType) ??
78
+ DEFAULT_SYSTEM_MESSAGE
79
+ );
80
+ }
81
+ default: {
82
+ const _exhaustiveCheck: never = role;
83
+ throw new Error(`Unknown message role: ${_exhaustiveCheck}`);
84
+ }
85
+ }
86
+ };
87
+
88
+ const ThreadMessageComponent: FC<{ components: MessageComponents }> = ({
89
+ components,
19
90
  }) => {
20
- const aui = useAui({
21
- message: Derived({
22
- source: "thread",
23
- query: { type: "index", index },
24
- get: (aui) => aui.threads().thread("main").message({ index }),
25
- }),
26
- });
27
-
28
- return <AuiProvider value={aui}>{children}</AuiProvider>;
91
+ const role = useAuiState((s) => s.message.role);
92
+ const isEditing = useAuiState((s) => s.message.composer.isEditing);
93
+ const Component = getComponent(components, role, isEditing);
94
+
95
+ return <Component />;
29
96
  };
30
97
 
31
- export const ThreadMessages = ({ renderMessage }: ThreadMessagesProps) => {
32
- const messages = useAuiState((s) => s.thread.messages);
98
+ const isComponentsSame = (prev: MessageComponents, next: MessageComponents) => {
99
+ return (
100
+ prev.Message === next.Message &&
101
+ prev.EditComposer === next.EditComposer &&
102
+ prev.UserEditComposer === next.UserEditComposer &&
103
+ prev.AssistantEditComposer === next.AssistantEditComposer &&
104
+ prev.SystemEditComposer === next.SystemEditComposer &&
105
+ prev.UserMessage === next.UserMessage &&
106
+ prev.AssistantMessage === next.AssistantMessage &&
107
+ prev.SystemMessage === next.SystemMessage
108
+ );
109
+ };
110
+
111
+ const ThreadMessageByIndex = memo(
112
+ ({ index, components }: { index: number; components: MessageComponents }) => {
113
+ return (
114
+ <MessageByIndexProvider index={index}>
115
+ <ThreadMessageComponent components={components} />
116
+ </MessageByIndexProvider>
117
+ );
118
+ },
119
+ (prev, next) =>
120
+ prev.index === next.index &&
121
+ isComponentsSame(prev.components, next.components),
122
+ );
123
+
124
+ export const ThreadMessages = ({ components }: ThreadMessagesProps) => {
125
+ const messagesLength = useAuiState((s) => s.thread.messages.length);
33
126
 
34
127
  return (
35
128
  <Box flexDirection="column">
36
- {(messages as unknown as ThreadMessage[]).map((message, index) => (
37
- <MessageScope key={message.id} index={index}>
38
- {renderMessage({ message, index })}
39
- </MessageScope>
129
+ {Array.from({ length: messagesLength }, (_, index) => (
130
+ <ThreadMessageByIndex
131
+ key={index}
132
+ index={index}
133
+ components={components}
134
+ />
40
135
  ))}
41
136
  </Box>
42
137
  );
@@ -1,16 +1,22 @@
1
- export { ThreadRoot, type ThreadRootProps } from "./ThreadRoot";
2
- export { ThreadMessages, type ThreadMessagesProps } from "./ThreadMessages";
3
1
  export {
4
- ThreadPrimitiveMessages,
5
- ThreadPrimitiveMessageByIndex,
6
- } from "@assistant-ui/core/react";
7
- export { ThreadEmpty, type ThreadEmptyProps } from "./ThreadEmpty";
8
- export { ThreadIf, type ThreadIfProps } from "./ThreadIf";
2
+ ThreadRoot as Root,
3
+ type ThreadRootProps as RootProps,
4
+ } from "./ThreadRoot";
5
+ export {
6
+ ThreadMessages as Messages,
7
+ type ThreadMessagesProps as MessagesProps,
8
+ } from "./ThreadMessages";
9
+ export { ThreadPrimitiveMessageByIndex as MessageByIndex } from "@assistant-ui/core/react";
10
+ export {
11
+ ThreadEmpty as Empty,
12
+ type ThreadEmptyProps as EmptyProps,
13
+ } from "./ThreadEmpty";
14
+ export { ThreadIf as If, type ThreadIfProps as IfProps } from "./ThreadIf";
9
15
  export {
10
- ThreadSuggestion,
11
- type ThreadSuggestionProps,
16
+ ThreadSuggestion as Suggestion,
17
+ type ThreadSuggestionProps as SuggestionProps,
12
18
  } from "./ThreadSuggestion";
13
19
  export {
14
- ThreadPrimitiveSuggestions,
15
- ThreadPrimitiveSuggestionByIndex,
20
+ ThreadPrimitiveSuggestions as Suggestions,
21
+ ThreadPrimitiveSuggestionByIndex as SuggestionByIndex,
16
22
  } from "@assistant-ui/core/react";
@@ -1,3 +1,12 @@
1
- export { ThreadListRoot, type ThreadListRootProps } from "./ThreadListRoot";
2
- export { ThreadListItems, type ThreadListItemsProps } from "./ThreadListItems";
3
- export { ThreadListNew, type ThreadListNewProps } from "./ThreadListNew";
1
+ export {
2
+ ThreadListRoot as Root,
3
+ type ThreadListRootProps as RootProps,
4
+ } from "./ThreadListRoot";
5
+ export {
6
+ ThreadListItems as Items,
7
+ type ThreadListItemsProps as ItemsProps,
8
+ } from "./ThreadListItems";
9
+ export {
10
+ ThreadListNew as New,
11
+ type ThreadListNewProps as NewProps,
12
+ } from "./ThreadListNew";
@@ -1,21 +1,21 @@
1
1
  export {
2
- ThreadListItemRoot,
3
- type ThreadListItemRootProps,
2
+ ThreadListItemRoot as Root,
3
+ type ThreadListItemRootProps as RootProps,
4
4
  } from "./ThreadListItemRoot";
5
- export { ThreadListItemPrimitiveTitle as ThreadListItemTitle } from "@assistant-ui/core/react";
5
+ export { ThreadListItemPrimitiveTitle as Title } from "@assistant-ui/core/react";
6
6
  export {
7
- ThreadListItemTrigger,
8
- type ThreadListItemTriggerProps,
7
+ ThreadListItemTrigger as Trigger,
8
+ type ThreadListItemTriggerProps as TriggerProps,
9
9
  } from "./ThreadListItemTrigger";
10
10
  export {
11
- ThreadListItemDelete,
12
- type ThreadListItemDeleteProps,
11
+ ThreadListItemDelete as Delete,
12
+ type ThreadListItemDeleteProps as DeleteProps,
13
13
  } from "./ThreadListItemDelete";
14
14
  export {
15
- ThreadListItemArchive,
16
- type ThreadListItemArchiveProps,
15
+ ThreadListItemArchive as Archive,
16
+ type ThreadListItemArchiveProps as ArchiveProps,
17
17
  } from "./ThreadListItemArchive";
18
18
  export {
19
- ThreadListItemUnarchive,
20
- type ThreadListItemUnarchiveProps,
19
+ ThreadListItemUnarchive as Unarchive,
20
+ type ThreadListItemUnarchiveProps as UnarchiveProps,
21
21
  } from "./ThreadListItemUnarchive";
@@ -0,0 +1,40 @@
1
+ import type { ReactElement } from "react";
2
+ import { afterEach, describe, expect, it } from "vitest";
3
+ import { cleanup, render } from "ink-testing-library";
4
+ import { ToolFallback } from "./ToolFallback";
5
+
6
+ const renderFrame = async (node: ReactElement) => {
7
+ const instance = render(node);
8
+ await new Promise((resolve) => setTimeout(resolve, 0));
9
+ return instance.lastFrame() ?? "";
10
+ };
11
+
12
+ afterEach(() => {
13
+ cleanup();
14
+ });
15
+
16
+ describe("ToolFallback", () => {
17
+ it("truncates expanded error output with maxResultLines", async () => {
18
+ const frame = await renderFrame(
19
+ <ToolFallback
20
+ expanded
21
+ maxResultLines={2}
22
+ type="tool-call"
23
+ toolCallId="tool-call-1"
24
+ toolName="search"
25
+ args={{}}
26
+ argsText="{}"
27
+ result={"line 1\nline 2\nline 3\nline 4"}
28
+ isError
29
+ status={{ type: "incomplete", reason: "error", error: "boom" }}
30
+ />,
31
+ );
32
+
33
+ expect(frame).toContain("Error:");
34
+ expect(frame).toContain("line 1");
35
+ expect(frame).toContain("line 2");
36
+ expect(frame).toContain("... (2 more lines)");
37
+ expect(frame).not.toContain("line 3");
38
+ expect(frame).not.toContain("line 4");
39
+ });
40
+ });