@assistant-ui/react 0.15.14 → 0.15.15

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 (160) hide show
  1. package/dist/assistant-transport.d.ts +32 -0
  2. package/dist/assistant-transport.d.ts.map +1 -0
  3. package/dist/assistant-transport.js +27 -0
  4. package/dist/assistant-transport.js.map +1 -0
  5. package/dist/context/providers/MessageProvider.js +1 -1
  6. package/dist/hooks/useMessageQuote.js +1 -1
  7. package/dist/hooks/useToolCallElapsed.js +1 -1
  8. package/dist/index.d.ts +4 -7
  9. package/dist/index.js +6 -8
  10. package/dist/legacy-runtime/AssistantRuntimeProvider.js +1 -1
  11. package/dist/mcp-apps/McpAppsRemoteHost.d.ts.map +1 -1
  12. package/dist/mcp-apps/McpAppsRemoteHost.js +18 -2
  13. package/dist/mcp-apps/McpAppsRemoteHost.js.map +1 -1
  14. package/dist/mcp-apps/bridge.js +39 -24
  15. package/dist/mcp-apps/bridge.js.map +1 -1
  16. package/dist/model-context/makeAssistantVisible.js +1 -1
  17. package/dist/primitives/actionBar/ActionBarCopy.js +1 -1
  18. package/dist/primitives/actionBar/ActionBarExportMarkdown.js +1 -1
  19. package/dist/primitives/actionBar/ActionBarFeedbackNegative.js +1 -1
  20. package/dist/primitives/actionBar/ActionBarFeedbackPositive.js +1 -1
  21. package/dist/primitives/actionBar/ActionBarStopSpeaking.d.ts.map +1 -1
  22. package/dist/primitives/actionBar/ActionBarStopSpeaking.js +24 -37
  23. package/dist/primitives/actionBar/ActionBarStopSpeaking.js.map +1 -1
  24. package/dist/primitives/actionBar/useActionBarFloatStatus.js +1 -1
  25. package/dist/primitives/assistantModal/AssistantModalRoot.js +1 -1
  26. package/dist/primitives/attachment/AttachmentName.js +1 -1
  27. package/dist/primitives/attachment/AttachmentRemove.js +1 -1
  28. package/dist/primitives/attachment/AttachmentThumb.js +1 -1
  29. package/dist/primitives/branchPicker/BranchPickerCount.js +1 -1
  30. package/dist/primitives/branchPicker/BranchPickerNumber.js +1 -1
  31. package/dist/primitives/chainOfThought/ChainOfThoughtAccordionTrigger.js +1 -1
  32. package/dist/primitives/composer/ComposerAddAttachment.js +1 -1
  33. package/dist/primitives/composer/ComposerAttachmentDropzone.js +1 -1
  34. package/dist/primitives/composer/ComposerDictationTranscript.js +1 -1
  35. package/dist/primitives/composer/ComposerInput.js +2 -2
  36. package/dist/primitives/composer/ComposerInput.js.map +1 -1
  37. package/dist/primitives/composer/ComposerQuote.js +1 -1
  38. package/dist/primitives/composer/ComposerRoot.js +1 -1
  39. package/dist/primitives/composer/ComposerStopDictation.js +1 -1
  40. package/dist/primitives/composer/trigger/TriggerPopover.js +1 -1
  41. package/dist/primitives/composer/useComposerInputState.js +1 -1
  42. package/dist/primitives/message/MessageIf.js +1 -1
  43. package/dist/primitives/message/MessagePartsGrouped.js +1 -1
  44. package/dist/primitives/message/MessageRoot.js +1 -1
  45. package/dist/primitives/queueItem/QueueItemRemove.js +1 -1
  46. package/dist/primitives/queueItem/QueueItemSteer.js +1 -1
  47. package/dist/primitives/queueItem/QueueItemText.js +1 -1
  48. package/dist/primitives/selectionToolbar/SelectionToolbarQuote.js +1 -1
  49. package/dist/primitives/suggestion/SuggestionDescription.js +1 -1
  50. package/dist/primitives/suggestion/SuggestionTitle.js +1 -1
  51. package/dist/primitives/suggestion/SuggestionTrigger.js +1 -1
  52. package/dist/primitives/thread/ThreadIf.js +1 -1
  53. package/dist/primitives/thread/ThreadRoot.d.ts.map +1 -1
  54. package/dist/primitives/thread/ThreadRoot.js +41 -9
  55. package/dist/primitives/thread/ThreadRoot.js.map +1 -1
  56. package/dist/primitives/thread/ThreadViewport.d.ts.map +1 -1
  57. package/dist/primitives/thread/ThreadViewport.js +2 -13
  58. package/dist/primitives/thread/ThreadViewport.js.map +1 -1
  59. package/dist/primitives/thread/useThreadViewportAutoScroll.d.ts.map +1 -1
  60. package/dist/primitives/thread/useThreadViewportAutoScroll.js +23 -8
  61. package/dist/primitives/thread/useThreadViewportAutoScroll.js.map +1 -1
  62. package/dist/primitives/threadList/ThreadListNew.js +1 -1
  63. package/dist/sandbox-host/SandboxHost.js +2 -0
  64. package/dist/sandbox-host/SandboxHost.js.map +1 -1
  65. package/dist/unstable/useComposerInput.d.ts +2 -2
  66. package/dist/unstable/useComposerInput.js +1 -1
  67. package/dist/unstable/useComposerInput.js.map +1 -1
  68. package/dist/unstable/useComposerInputHistory.js +1 -1
  69. package/dist/unstable/useComposerInputHistory.js.map +1 -1
  70. package/dist/unstable/useLiveCompletionAdapter.d.ts +5 -0
  71. package/dist/unstable/useLiveCompletionAdapter.d.ts.map +1 -1
  72. package/dist/unstable/useLiveCompletionAdapter.js +16 -3
  73. package/dist/unstable/useLiveCompletionAdapter.js.map +1 -1
  74. package/dist/unstable/useMentionAdapter.d.ts +1 -1
  75. package/dist/utils/smooth/SmoothContext.js +1 -1
  76. package/dist/utils/useToolArgsFieldStatus.js +1 -1
  77. package/package.json +9 -9
  78. package/src/assistant-transport.ts +94 -0
  79. package/src/index.ts +8 -4
  80. package/src/mcp-apps/McpAppsRemoteHost.test.ts +29 -3
  81. package/src/mcp-apps/McpAppsRemoteHost.ts +42 -7
  82. package/src/mcp-apps/bridge.test.ts +132 -0
  83. package/src/mcp-apps/bridge.ts +54 -30
  84. package/src/primitives/actionBar/ActionBarStopSpeaking.tsx +0 -9
  85. package/src/primitives/composer/ComposerInput.tsx +1 -1
  86. package/src/primitives/thread/ThreadRoot.test.tsx +251 -0
  87. package/src/primitives/thread/ThreadRoot.tsx +30 -0
  88. package/src/primitives/thread/ThreadViewport.tsx +1 -8
  89. package/src/primitives/thread/useThreadViewportAutoScroll.test.tsx +152 -0
  90. package/src/primitives/thread/useThreadViewportAutoScroll.ts +26 -19
  91. package/src/sandbox-host/SandboxHost.test.tsx +29 -0
  92. package/src/sandbox-host/SandboxHost.tsx +2 -0
  93. package/src/tests/RemoteThreadListRuntime.adapterProvider.test.tsx +24 -3
  94. package/src/tests/RemoteThreadListRuntime.reloadMainThread.test.tsx +1 -2
  95. package/src/tests/augmentations.test.ts +49 -0
  96. package/src/tests/in-memory-thread-list.test.tsx +1 -1
  97. package/src/tests/threadMessageById.test.tsx +61 -1
  98. package/src/unstable/useComposerInput.ts +2 -2
  99. package/src/unstable/useComposerInputHistory.test.tsx +4 -4
  100. package/src/unstable/useComposerInputHistory.ts +1 -1
  101. package/src/unstable/useLiveCompletionAdapter.test.tsx +100 -0
  102. package/src/unstable/useLiveCompletionAdapter.ts +41 -17
  103. package/dist/client/InMemoryThreadList.d.ts +0 -12
  104. package/dist/client/InMemoryThreadList.d.ts.map +0 -1
  105. package/dist/client/InMemoryThreadList.js +0 -374
  106. package/dist/client/InMemoryThreadList.js.map +0 -1
  107. package/dist/legacy-runtime/cloud/auiV0.d.ts +0 -119
  108. package/dist/legacy-runtime/cloud/auiV0.d.ts.map +0 -1
  109. package/dist/legacy-runtime/cloud/auiV0.js +0 -137
  110. package/dist/legacy-runtime/cloud/auiV0.js.map +0 -1
  111. package/dist/legacy-runtime/cloud/useCloudThreadListRuntime.d.ts +0 -16
  112. package/dist/legacy-runtime/cloud/useCloudThreadListRuntime.d.ts.map +0 -1
  113. package/dist/legacy-runtime/cloud/useCloudThreadListRuntime.js +0 -36
  114. package/dist/legacy-runtime/cloud/useCloudThreadListRuntime.js.map +0 -1
  115. package/dist/legacy-runtime/runtime-cores/assistant-transport/commandQueue.d.ts +0 -17
  116. package/dist/legacy-runtime/runtime-cores/assistant-transport/commandQueue.d.ts.map +0 -1
  117. package/dist/legacy-runtime/runtime-cores/assistant-transport/commandQueue.js +0 -55
  118. package/dist/legacy-runtime/runtime-cores/assistant-transport/commandQueue.js.map +0 -1
  119. package/dist/legacy-runtime/runtime-cores/assistant-transport/replayBoundaryStream.d.ts +0 -11
  120. package/dist/legacy-runtime/runtime-cores/assistant-transport/replayBoundaryStream.d.ts.map +0 -1
  121. package/dist/legacy-runtime/runtime-cores/assistant-transport/replayBoundaryStream.js +0 -165
  122. package/dist/legacy-runtime/runtime-cores/assistant-transport/replayBoundaryStream.js.map +0 -1
  123. package/dist/legacy-runtime/runtime-cores/assistant-transport/runManager.d.ts +0 -15
  124. package/dist/legacy-runtime/runtime-cores/assistant-transport/runManager.d.ts.map +0 -1
  125. package/dist/legacy-runtime/runtime-cores/assistant-transport/runManager.js +0 -81
  126. package/dist/legacy-runtime/runtime-cores/assistant-transport/runManager.js.map +0 -1
  127. package/dist/legacy-runtime/runtime-cores/assistant-transport/types.d.ts +0 -126
  128. package/dist/legacy-runtime/runtime-cores/assistant-transport/types.d.ts.map +0 -1
  129. package/dist/legacy-runtime/runtime-cores/assistant-transport/types.js +0 -0
  130. package/dist/legacy-runtime/runtime-cores/assistant-transport/useAssistantTransportRuntime.d.ts +0 -14
  131. package/dist/legacy-runtime/runtime-cores/assistant-transport/useAssistantTransportRuntime.d.ts.map +0 -1
  132. package/dist/legacy-runtime/runtime-cores/assistant-transport/useAssistantTransportRuntime.js +0 -290
  133. package/dist/legacy-runtime/runtime-cores/assistant-transport/useAssistantTransportRuntime.js.map +0 -1
  134. package/dist/legacy-runtime/runtime-cores/assistant-transport/useConvertedState.d.ts +0 -7
  135. package/dist/legacy-runtime/runtime-cores/assistant-transport/useConvertedState.d.ts.map +0 -1
  136. package/dist/legacy-runtime/runtime-cores/assistant-transport/useConvertedState.js +0 -25
  137. package/dist/legacy-runtime/runtime-cores/assistant-transport/useConvertedState.js.map +0 -1
  138. package/dist/legacy-runtime/runtime-cores/assistant-transport/useLatestRef.d.ts +0 -7
  139. package/dist/legacy-runtime/runtime-cores/assistant-transport/useLatestRef.d.ts.map +0 -1
  140. package/dist/legacy-runtime/runtime-cores/assistant-transport/useLatestRef.js +0 -27
  141. package/dist/legacy-runtime/runtime-cores/assistant-transport/useLatestRef.js.map +0 -1
  142. package/dist/legacy-runtime/runtime-cores/assistant-transport/utils.d.ts +0 -2
  143. package/dist/legacy-runtime/runtime-cores/assistant-transport/utils.js +0 -2
  144. package/src/client/InMemoryThreadList.ts +0 -247
  145. package/src/legacy-runtime/cloud/auiV0.ts +0 -331
  146. package/src/legacy-runtime/cloud/useCloudThreadListRuntime.ts +0 -31
  147. package/src/legacy-runtime/runtime-cores/assistant-transport/commandQueue.ts +0 -68
  148. package/src/legacy-runtime/runtime-cores/assistant-transport/replayBoundaryStream.test.ts +0 -455
  149. package/src/legacy-runtime/runtime-cores/assistant-transport/replayBoundaryStream.ts +0 -172
  150. package/src/legacy-runtime/runtime-cores/assistant-transport/runManager.ts +0 -113
  151. package/src/legacy-runtime/runtime-cores/assistant-transport/transport-delivery.test.tsx +0 -79
  152. package/src/legacy-runtime/runtime-cores/assistant-transport/transport-parentid.test.tsx +0 -97
  153. package/src/legacy-runtime/runtime-cores/assistant-transport/transport-scheduling.test.ts +0 -307
  154. package/src/legacy-runtime/runtime-cores/assistant-transport/types.ts +0 -170
  155. package/src/legacy-runtime/runtime-cores/assistant-transport/useAssistantTransport.spec.md +0 -141
  156. package/src/legacy-runtime/runtime-cores/assistant-transport/useAssistantTransportRuntime.test.tsx +0 -585
  157. package/src/legacy-runtime/runtime-cores/assistant-transport/useAssistantTransportRuntime.ts +0 -476
  158. package/src/legacy-runtime/runtime-cores/assistant-transport/useConvertedState.ts +0 -20
  159. package/src/legacy-runtime/runtime-cores/assistant-transport/useLatestRef.ts +0 -9
  160. package/src/legacy-runtime/runtime-cores/assistant-transport/utils.ts +0 -1
@@ -26,9 +26,14 @@ const dummyHistory: ThreadHistoryAdapter = {
26
26
  append: async () => {},
27
27
  };
28
28
 
29
- const makeRuntimeHook = (capture: { adapters: CapturedAdapters }) =>
29
+ const makeRuntimeHook = (capture: {
30
+ adapters: CapturedAdapters;
31
+ calls?: CapturedAdapters[];
32
+ }) =>
30
33
  function useTestRuntimeHook() {
31
- capture.adapters = useRuntimeAdapters();
34
+ const adapters = useRuntimeAdapters();
35
+ capture.calls?.push(adapters);
36
+ capture.adapters = adapters;
32
37
  return useLocalRuntime(noOpAdapter);
33
38
  };
34
39
 
@@ -68,12 +73,16 @@ const wrapInRuntimeAdapterProvider = (
68
73
 
69
74
  describe("RemoteThreadListAdapter.unstable_Provider", () => {
70
75
  it("makes Provider context visible to the runtime hook while preserving outer modelContext", async () => {
71
- const capture: { adapters: CapturedAdapters } = { adapters: null };
76
+ const capture: { adapters: CapturedAdapters; calls: CapturedAdapters[] } = {
77
+ adapters: null,
78
+ calls: [],
79
+ };
72
80
  const adapter = makeAdapter({
73
81
  unstable_Provider: wrapInRuntimeAdapterProvider(dummyHistory),
74
82
  });
75
83
  await renderAndWaitForBinder(adapter, capture);
76
84
 
85
+ expect(capture.calls[0]?.history).toBe(dummyHistory);
77
86
  expect(capture.adapters?.history).toBe(dummyHistory);
78
87
  expect(capture.adapters?.modelContext).toBeDefined();
79
88
  });
@@ -86,6 +95,18 @@ describe("RemoteThreadListAdapter.unstable_Provider", () => {
86
95
  expect(capture.adapters?.history).toBeUndefined();
87
96
  });
88
97
 
98
+ it("makes useAdapters context visible when no Provider is supplied", async () => {
99
+ const capture: { adapters: CapturedAdapters } = { adapters: null };
100
+ const adapter = makeAdapter({
101
+ unstable_useAdapters: function useTestAdapters() {
102
+ return { history: dummyHistory };
103
+ },
104
+ });
105
+ await renderAndWaitForBinder(adapter, capture);
106
+
107
+ expect(capture.adapters?.history).toBe(dummyHistory);
108
+ });
109
+
89
110
  it("picks up a swapped Provider on re-render", async () => {
90
111
  const capture: { adapters: CapturedAdapters } = { adapters: null };
91
112
  const firstHistory: ThreadHistoryAdapter = {
@@ -86,8 +86,7 @@ describe("threads.reloadMainThread", () => {
86
86
  await runtime.threads.reloadMainThread();
87
87
  });
88
88
 
89
- // the hook ran again, which is what re-runs the adapter's load()
90
- await waitFor(() => expect(mounts.count).toBe(beforeReload + 1));
89
+ await waitFor(() => expect(mounts.count).toBeGreaterThan(beforeReload));
91
90
  });
92
91
 
93
92
  it("keeps a thread runtime readable across the remount", async () => {
@@ -1,5 +1,11 @@
1
1
  import { describe, expectTypeOf, it } from "vitest";
2
2
  import type { UserCommands, UserExternalState } from "../augmentations";
3
+ import type {
4
+ AssistantTransportCommand,
5
+ useAssistantTransportRuntime,
6
+ useAssistantTransportSendCommand,
7
+ useAssistantTransportState,
8
+ } from "../index";
3
9
 
4
10
  declare module "../augmentations" {
5
11
  namespace Assistant {
@@ -36,4 +42,47 @@ describe("Assistant augmentations", () => {
36
42
  expectTypeOf<{ type: "core-command" }>().toExtend<UserCommands>();
37
43
  expectTypeOf<{ fromCore: boolean }>().toExtend<UserExternalState>();
38
44
  });
45
+
46
+ it("react-side contributions reach the public transport types", () => {
47
+ expectTypeOf<{
48
+ type: "react-command";
49
+ data: number;
50
+ }>().toExtend<AssistantTransportCommand>();
51
+ expectTypeOf<{ fromReact: boolean }>().toExtend<
52
+ ReturnType<typeof useAssistantTransportState>
53
+ >();
54
+ expectTypeOf<{
55
+ type: "react-command";
56
+ data: number;
57
+ }>().toExtend<
58
+ Parameters<ReturnType<typeof useAssistantTransportSendCommand>>[0]
59
+ >();
60
+ type RuntimeOptions = Parameters<typeof useAssistantTransportRuntime>[0];
61
+ expectTypeOf<{
62
+ type: "react-command";
63
+ data: number;
64
+ }>().toExtend<
65
+ Parameters<RuntimeOptions["converter"]>[1]["pendingCommands"][number]
66
+ >();
67
+ expectTypeOf<{
68
+ type: "react-command";
69
+ data: number;
70
+ }>().toExtend<
71
+ Parameters<NonNullable<RuntimeOptions["onError"]>>[1]["commands"][number]
72
+ >();
73
+ expectTypeOf<{
74
+ type: "react-command";
75
+ data: number;
76
+ }>().toExtend<
77
+ Parameters<NonNullable<RuntimeOptions["onCancel"]>>[0]["commands"][number]
78
+ >();
79
+ expectTypeOf<{
80
+ type: "react-command";
81
+ data: number;
82
+ }>().toExtend<
83
+ Parameters<
84
+ NonNullable<RuntimeOptions["prepareSendCommandsRequest"]>
85
+ >[0]["commands"][number]
86
+ >();
87
+ });
39
88
  });
@@ -4,7 +4,7 @@ import { render, waitFor } from "@testing-library/react";
4
4
  import type { FC } from "react";
5
5
  import { describe, it, expect, vi } from "vitest";
6
6
  import { useAui, AuiProvider } from "@assistant-ui/store";
7
- import { InMemoryThreadList } from "../client/InMemoryThreadList";
7
+ import { InMemoryThreadList } from "@assistant-ui/core/store";
8
8
  import { ExternalThread } from "../index";
9
9
 
10
10
  const renderThreads = () => {
@@ -3,6 +3,7 @@
3
3
  import { act, render, screen } from "@testing-library/react";
4
4
  import { useState, type FC, type PropsWithChildren } from "react";
5
5
  import { describe, expect, it } from "vitest";
6
+ import { useAui } from "@assistant-ui/store";
6
7
  import {
7
8
  AssistantRuntimeProvider,
8
9
  useExternalStoreRuntime,
@@ -13,7 +14,11 @@ import * as ThreadPrimitive from "../primitives/thread";
13
14
  import * as MessagePrimitive from "../primitives/message";
14
15
  import * as MessagePartPrimitive from "../primitives/messagePart";
15
16
 
16
- type Msg = { id: string; role: "user" | "assistant"; text: string };
17
+ type Msg = {
18
+ id: string;
19
+ role: "user" | "assistant" | "system";
20
+ text: string;
21
+ };
17
22
 
18
23
  const convertMessage = (m: Msg): ThreadMessageLike => ({
19
24
  id: m.id,
@@ -39,6 +44,7 @@ const Provider: FC<PropsWithChildren<{ initial: Msg[] }>> = ({
39
44
  messages,
40
45
  convertMessage,
41
46
  onNew: async () => {},
47
+ onEdit: async () => {},
42
48
  });
43
49
  return (
44
50
  <AssistantRuntimeProvider runtime={runtime}>
@@ -47,6 +53,19 @@ const Provider: FC<PropsWithChildren<{ initial: Msg[] }>> = ({
47
53
  );
48
54
  };
49
55
 
56
+ let beginEdit: (() => void) | undefined;
57
+
58
+ const CaptureBeginEdit: FC = () => {
59
+ const aui = useAui();
60
+ beginEdit = () => aui.thread.message({ index: 0 }).composer().beginEdit();
61
+ return null;
62
+ };
63
+
64
+ const SYSTEM_COMPONENTS = {
65
+ UserMessage: () => <span>user</span>,
66
+ AssistantMessage: () => <span>assistant</span>,
67
+ };
68
+
50
69
  let lastIds: readonly string[] | undefined;
51
70
  const idIdentities: (readonly string[])[] = [];
52
71
 
@@ -215,3 +234,44 @@ describe("ThreadPrimitive.Unstable_MessageById", () => {
215
234
  expect(screen.getByTestId("by-id").textContent).toBe("first");
216
235
  });
217
236
  });
237
+
238
+ describe("system message editing fallback", () => {
239
+ it.each([
240
+ [
241
+ "Messages",
242
+ () => <ThreadPrimitive.Messages components={SYSTEM_COMPONENTS} />,
243
+ ],
244
+ [
245
+ "MessageByIndex",
246
+ () => (
247
+ <ThreadPrimitive.MessageByIndex
248
+ index={0}
249
+ components={SYSTEM_COMPONENTS}
250
+ />
251
+ ),
252
+ ],
253
+ [
254
+ "Unstable_MessageById",
255
+ () => (
256
+ <ThreadPrimitive.Unstable_MessageById
257
+ messageId="s1"
258
+ components={SYSTEM_COMPONENTS}
259
+ />
260
+ ),
261
+ ],
262
+ ])(
263
+ "renders nothing for a system message in edit mode through %s",
264
+ async (_, renderMessage) => {
265
+ const view = render(
266
+ <Provider initial={[{ id: "s1", role: "system", text: "sys" }]}>
267
+ <CaptureBeginEdit />
268
+ {renderMessage()}
269
+ </Provider>,
270
+ );
271
+
272
+ await act(async () => beginEdit!());
273
+
274
+ expect(view.container.textContent).toBe("");
275
+ },
276
+ );
277
+ });
@@ -26,8 +26,8 @@ export type Unstable_ComposerInput = {
26
26
  value: string;
27
27
  /**
28
28
  * Writes `text` into the composer, mirroring `ComposerPrimitive.Input`:
29
- * a no-op unless the composer is editing, committed via `flushTapSync` so the
30
- * controlled value stays in sync within the same tick.
29
+ * a no-op unless the composer is editing; the controlled value stays in
30
+ * sync within the same tick.
31
31
  */
32
32
  setText(text: string): void;
33
33
  /**
@@ -6,7 +6,7 @@ const fixture = {
6
6
  messages: [] as { role: string; content: { type: string; text: string }[] }[],
7
7
  composerType: "thread",
8
8
  activeAria: null as object | null,
9
- switchedToHandlers: [] as (() => void)[],
9
+ selectionChangedHandlers: [] as (() => void)[],
10
10
  };
11
11
  const setText = vi.fn();
12
12
 
@@ -18,7 +18,7 @@ vi.mock("@assistant-ui/store", () => ({
18
18
  },
19
19
  thread: { getState: () => ({ messages: fixture.messages }) },
20
20
  on: (_event: string, cb: () => void) => {
21
- fixture.switchedToHandlers.push(cb);
21
+ fixture.selectionChangedHandlers.push(cb);
22
22
  return () => {};
23
23
  },
24
24
  }),
@@ -71,7 +71,7 @@ beforeEach(() => {
71
71
  fixture.messages = [user("first"), assistant("reply"), user("second")];
72
72
  fixture.composerType = "thread";
73
73
  fixture.activeAria = null;
74
- fixture.switchedToHandlers = [];
74
+ fixture.selectionChangedHandlers = [];
75
75
  setText.mockClear();
76
76
  });
77
77
 
@@ -192,7 +192,7 @@ describe("unstable_useComposerInputHistory", () => {
192
192
  it("resets browsing when the thread switches", () => {
193
193
  const { textarea } = setup("");
194
194
  arrow(textarea, "ArrowUp");
195
- fixture.switchedToHandlers.forEach((cb) => cb());
195
+ fixture.selectionChangedHandlers.forEach((cb) => cb());
196
196
  textarea.value = "second";
197
197
  const notPrevented = arrow(textarea, "ArrowDown");
198
198
  expect(notPrevented).toBe(true);
@@ -72,7 +72,7 @@ export function unstable_useComposerInputHistory(): Unstable_ComposerInputHistor
72
72
  useEffect(() => {
73
73
  if (aui.composer.getState().type !== "thread") return undefined;
74
74
 
75
- return aui.on("threadListItem.switchedTo", () => {
75
+ return aui.on("threads.selectionChanged", () => {
76
76
  browseRef.current = null;
77
77
  });
78
78
  }, [aui]);
@@ -129,6 +129,80 @@ describe("unstable_useLiveCompletionAdapter", () => {
129
129
  expect(result.current.adapter.search!("ab")).toEqual([item("ab")]);
130
130
  });
131
131
 
132
+ it("refreshes cached results when the fetcher cache key changes", async () => {
133
+ const fetcherA = vi.fn(async () => [item("workspace-a")]);
134
+ const fetcherB = vi.fn(async () => [item("workspace-b")]);
135
+ const { result, rerender } = renderHook(
136
+ ({ fetcher, cacheKey }) =>
137
+ unstable_useLiveCompletionAdapter({
138
+ fetcher,
139
+ cacheKey,
140
+ debounceMs: 0,
141
+ }),
142
+ { initialProps: { fetcher: fetcherA, cacheKey: "workspace-a" } },
143
+ );
144
+
145
+ await act(async () => {
146
+ result.current.adapter.search!("alice");
147
+ await vi.advanceTimersByTimeAsync(0);
148
+ });
149
+ expect(result.current.adapter.search!("alice")).toEqual([
150
+ item("workspace-a"),
151
+ ]);
152
+
153
+ await act(async () => {
154
+ rerender({ fetcher: fetcherB, cacheKey: "workspace-b" });
155
+ });
156
+ expect(result.current.adapter.search!("alice")).toEqual([]);
157
+
158
+ await act(async () => {
159
+ await vi.advanceTimersByTimeAsync(0);
160
+ });
161
+ expect(fetcherB).toHaveBeenCalledWith("alice");
162
+ expect(result.current.adapter.search!("alice")).toEqual([
163
+ item("workspace-b"),
164
+ ]);
165
+ });
166
+
167
+ it("drops pending results after the fetcher cache key changes", async () => {
168
+ let resolveA!: (items: readonly Unstable_TriggerItem[]) => void;
169
+ const fetcherA = vi.fn(
170
+ () =>
171
+ new Promise<readonly Unstable_TriggerItem[]>((resolve) => {
172
+ resolveA = resolve;
173
+ }),
174
+ );
175
+ const fetcherB = vi.fn(async () => [item("workspace-b")]);
176
+ const { result, rerender } = renderHook(
177
+ ({ fetcher, cacheKey }) =>
178
+ unstable_useLiveCompletionAdapter({
179
+ fetcher,
180
+ cacheKey,
181
+ debounceMs: 0,
182
+ }),
183
+ { initialProps: { fetcher: fetcherA, cacheKey: "workspace-a" } },
184
+ );
185
+
186
+ await act(async () => {
187
+ result.current.adapter.search!("alice");
188
+ await vi.advanceTimersByTimeAsync(0);
189
+ });
190
+ await act(async () => {
191
+ rerender({ fetcher: fetcherB, cacheKey: "workspace-b" });
192
+ });
193
+ await act(async () => {
194
+ result.current.adapter.search!("alice");
195
+ await vi.advanceTimersByTimeAsync(0);
196
+ });
197
+ await act(async () => {
198
+ resolveA([item("workspace-a")]);
199
+ });
200
+
201
+ expect(result.current.adapter.search!("alice")).toEqual([
202
+ item("workspace-b"),
203
+ ]);
204
+ });
205
+
132
206
  it("allows a failed query to be retried", async () => {
133
207
  const fetcher = vi
134
208
  .fn<(query: string) => Promise<readonly Unstable_TriggerItem[]>>()
@@ -156,6 +230,32 @@ describe("unstable_useLiveCompletionAdapter", () => {
156
230
  expect(result.current.adapter.search!("alice")).toEqual([item("alice")]);
157
231
  });
158
232
 
233
+ it("allows a synchronously failed query to be retried", async () => {
234
+ const fetcher = vi
235
+ .fn<(query: string) => Promise<readonly Unstable_TriggerItem[]>>()
236
+ .mockImplementationOnce(() => {
237
+ throw new Error("invalid request configuration");
238
+ })
239
+ .mockResolvedValueOnce([item("alice")]);
240
+ const { result } = renderHook(() =>
241
+ unstable_useLiveCompletionAdapter({ fetcher, debounceMs: 0 }),
242
+ );
243
+
244
+ await act(async () => {
245
+ result.current.adapter.search!("alice");
246
+ await vi.advanceTimersByTimeAsync(0);
247
+ });
248
+ expect(fetcher).toHaveBeenCalledTimes(1);
249
+ expect(result.current.isLoading).toBe(false);
250
+
251
+ await act(async () => {
252
+ result.current.adapter.search!("alice");
253
+ await vi.advanceTimersByTimeAsync(0);
254
+ });
255
+ expect(fetcher).toHaveBeenCalledTimes(2);
256
+ expect(result.current.adapter.search!("alice")).toEqual([item("alice")]);
257
+ });
258
+
159
259
  it("does not automatically retry when search runs during every render", async () => {
160
260
  const fetcher = vi
161
261
  .fn<(query: string) => Promise<readonly Unstable_TriggerItem[]>>()
@@ -1,6 +1,13 @@
1
1
  "use client";
2
2
 
3
- import { useCallback, useEffect, useMemo, useRef, useState } from "react";
3
+ import {
4
+ useCallback,
5
+ useEffect,
6
+ useLayoutEffect,
7
+ useMemo,
8
+ useRef,
9
+ useState,
10
+ } from "react";
4
11
  import type {
5
12
  Unstable_TriggerAdapter,
6
13
  Unstable_TriggerItem,
@@ -13,6 +20,11 @@ export type Unstable_UseLiveCompletionAdapterOptions = {
13
20
  * next render.
14
21
  */
15
22
  readonly fetcher: (query: string) => Promise<readonly Unstable_TriggerItem[]>;
23
+ /**
24
+ * Identifies the fetcher's data source. Change this when switching accounts,
25
+ * workspaces, or another boundary that should invalidate cached results.
26
+ */
27
+ readonly cacheKey?: string | number | undefined;
16
28
  /** Debounce applied before a fetch fires, in milliseconds. @default 60 */
17
29
  readonly debounceMs?: number | undefined;
18
30
  /** When `false`, no fetch is scheduled and the adapter stays empty. @default true */
@@ -52,7 +64,7 @@ const NO_QUERY = "\u0000";
52
64
  export function unstable_useLiveCompletionAdapter(
53
65
  options: Unstable_UseLiveCompletionAdapterOptions,
54
66
  ): { adapter: Unstable_TriggerAdapter; isLoading: boolean } {
55
- const { fetcher, debounceMs = 60, enabled = true } = options;
67
+ const { fetcher, cacheKey, debounceMs = 60, enabled = true } = options;
56
68
 
57
69
  const [state, setState] = useState<{
58
70
  query: string;
@@ -99,21 +111,23 @@ export function unstable_useLiveCompletionAdapter(
99
111
  setIsLoading(true);
100
112
  timerRef.current = setTimeout(() => {
101
113
  timerRef.current = null;
102
- fetcherRef.current(query).then(
103
- (items) => {
104
- if (token !== tokenRef.current) return;
105
- pendingRetryQueryRef.current = null;
106
- setState({ query, items, failed: false });
107
- setIsLoading(false);
108
- },
109
- () => {
110
- if (token !== tokenRef.current) return;
111
- pendingQueryRef.current = null;
112
- pendingRetryQueryRef.current = null;
113
- setState({ query, items: [], failed: true });
114
- setIsLoading(false);
115
- },
116
- );
114
+ Promise.resolve()
115
+ .then(() => fetcherRef.current(query))
116
+ .then(
117
+ (items) => {
118
+ if (token !== tokenRef.current) return;
119
+ pendingRetryQueryRef.current = null;
120
+ setState({ query, items, failed: false });
121
+ setIsLoading(false);
122
+ },
123
+ () => {
124
+ if (token !== tokenRef.current) return;
125
+ pendingQueryRef.current = null;
126
+ pendingRetryQueryRef.current = null;
127
+ setState({ query, items: [], failed: true });
128
+ setIsLoading(false);
129
+ },
130
+ );
117
131
  }, debounceMs);
118
132
  },
119
133
  [enabled, debounceMs, cancelTimer, rearmPendingRetry],
@@ -127,6 +141,16 @@ export function unstable_useLiveCompletionAdapter(
127
141
  setIsLoading(false);
128
142
  }, [cancelTimer, rearmPendingRetry]);
129
143
 
144
+ const cacheKeyRef = useRef(cacheKey);
145
+ useLayoutEffect(() => {
146
+ if (cacheKeyRef.current === cacheKey) return;
147
+ cacheKeyRef.current = cacheKey;
148
+ invalidatePending();
149
+ retryableQueryRef.current = null;
150
+ pendingRetryQueryRef.current = null;
151
+ setState({ query: NO_QUERY, items: [], failed: false });
152
+ }, [cacheKey, invalidatePending]);
153
+
130
154
  useEffect(() => {
131
155
  if (enabled) return;
132
156
  invalidatePending();
@@ -1,12 +0,0 @@
1
- import { ClientOutput } from "@assistant-ui/store";
2
- import { ResourceElement } from "@assistant-ui/tap";
3
- //#region src/client/InMemoryThreadList.d.ts
4
- type InMemoryThreadListProps = {
5
- thread: (threadId: string) => ResourceElement<ClientOutput<"thread">>;
6
- onSwitchToThread?: (threadId: string) => void;
7
- onSwitchToNewThread?: () => void;
8
- };
9
- declare const InMemoryThreadList: import("@assistant-ui/tap").Resource<ClientOutput<"threads">, [props: InMemoryThreadListProps]>;
10
- //#endregion
11
- export { InMemoryThreadList, InMemoryThreadListProps };
12
- //# sourceMappingURL=InMemoryThreadList.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"InMemoryThreadList.d.ts","names":[],"sources":["../../src/client/InMemoryThreadList.ts"],"mappings":";;;KAgBY;EACV,SAAS,qBAAqB,gBAAgB;EAC9C,oBAAoB;EACpB;;cAgMW,gDAAkB,SAAA,0BAAA,OAAA"}