@assistant-ui/react 0.14.22 → 0.14.24

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 (38) hide show
  1. package/dist/index.d.ts +4 -3
  2. package/dist/index.js +4 -3
  3. package/dist/legacy-runtime/runtime-cores/assistant-transport/commandQueue.d.ts +3 -1
  4. package/dist/legacy-runtime/runtime-cores/assistant-transport/commandQueue.d.ts.map +1 -1
  5. package/dist/legacy-runtime/runtime-cores/assistant-transport/commandQueue.js +2 -2
  6. package/dist/legacy-runtime/runtime-cores/assistant-transport/commandQueue.js.map +1 -1
  7. package/dist/legacy-runtime/runtime-cores/assistant-transport/useAssistantTransportRuntime.d.ts.map +1 -1
  8. package/dist/legacy-runtime/runtime-cores/assistant-transport/useAssistantTransportRuntime.js +6 -2
  9. package/dist/legacy-runtime/runtime-cores/assistant-transport/useAssistantTransportRuntime.js.map +1 -1
  10. package/dist/primitives/composer/ComposerInput.d.ts.map +1 -1
  11. package/dist/primitives/composer/ComposerInput.js +5 -14
  12. package/dist/primitives/composer/ComposerInput.js.map +1 -1
  13. package/dist/primitives/composer/useComposerInputState.d.ts +13 -0
  14. package/dist/primitives/composer/useComposerInputState.d.ts.map +1 -0
  15. package/dist/primitives/composer/useComposerInputState.js +47 -0
  16. package/dist/primitives/composer/useComposerInputState.js.map +1 -0
  17. package/dist/primitives/thread/ThreadMessages.d.ts +2 -2
  18. package/dist/primitives/thread/ThreadMessages.js +2 -2
  19. package/dist/primitives/thread.d.ts +3 -3
  20. package/dist/primitives/thread.js +3 -2
  21. package/dist/primitives/thread.js.map +1 -1
  22. package/dist/unstable/useComposerInput.d.ts +84 -0
  23. package/dist/unstable/useComposerInput.d.ts.map +1 -0
  24. package/dist/unstable/useComposerInput.js +78 -0
  25. package/dist/unstable/useComposerInput.js.map +1 -0
  26. package/package.json +13 -13
  27. package/src/index.ts +39 -0
  28. package/src/legacy-runtime/runtime-cores/assistant-transport/commandQueue.ts +5 -2
  29. package/src/legacy-runtime/runtime-cores/assistant-transport/transport-scheduling.test.ts +22 -0
  30. package/src/legacy-runtime/runtime-cores/assistant-transport/useAssistantTransportRuntime.ts +12 -2
  31. package/src/primitives/composer/ComposerInput.tsx +9 -20
  32. package/src/primitives/composer/trigger/triggerKeyboardResource.test.ts +18 -22
  33. package/src/primitives/composer/useComposerInputState.ts +34 -0
  34. package/src/primitives/thread/ThreadMessages.tsx +1 -0
  35. package/src/primitives/thread.ts +1 -0
  36. package/src/tests/threadMessageById.test.tsx +224 -0
  37. package/src/unstable/useComposerInput.test.tsx +187 -0
  38. package/src/unstable/useComposerInput.ts +126 -0
@@ -4,15 +4,15 @@ import { ThreadPrimitiveEmpty } from "./thread/ThreadEmpty.js";
4
4
  import { ThreadPrimitiveIf } from "./thread/ThreadIf.js";
5
5
  import { ThreadPrimitiveViewport } from "./thread/ThreadViewport.js";
6
6
  import { ThreadPrimitiveViewportFooter } from "./thread/ThreadViewportFooter.js";
7
- import { ThreadPrimitiveMessageByIndex, ThreadPrimitiveMessages } from "./thread/ThreadMessages.js";
7
+ import { ThreadPrimitiveMessageByIndex, ThreadPrimitiveMessages, ThreadPrimitiveUnstable_MessageById } from "./thread/ThreadMessages.js";
8
8
  import { ThreadPrimitiveScrollToBottom } from "./thread/ThreadScrollToBottom.js";
9
9
  import { ThreadPrimitiveSuggestion } from "./thread/ThreadSuggestion.js";
10
10
  import { ThreadPrimitiveSuggestionByIndex, ThreadPrimitiveSuggestions } from "./thread/ThreadSuggestions.js";
11
11
 
12
12
  //#region src/primitives/thread.d.ts
13
13
  declare namespace thread_d_exports {
14
- export { ThreadPrimitiveEmpty as Empty, ThreadPrimitiveIf as If, ThreadPrimitiveMessageByIndex as MessageByIndex, ThreadPrimitiveMessages as Messages, ThreadPrimitiveRoot as Root, ThreadPrimitiveScrollToBottom as ScrollToBottom, ThreadPrimitiveSuggestion as Suggestion, ThreadPrimitiveSuggestionByIndex as SuggestionByIndex, ThreadPrimitiveSuggestions as Suggestions, ThreadPrimitiveViewport as Viewport, ThreadPrimitiveViewportFooter as ViewportFooter, ThreadPrimitiveViewportProvider as ViewportProvider };
14
+ export { ThreadPrimitiveEmpty as Empty, ThreadPrimitiveIf as If, ThreadPrimitiveMessageByIndex as MessageByIndex, ThreadPrimitiveMessages as Messages, ThreadPrimitiveRoot as Root, ThreadPrimitiveScrollToBottom as ScrollToBottom, ThreadPrimitiveSuggestion as Suggestion, ThreadPrimitiveSuggestionByIndex as SuggestionByIndex, ThreadPrimitiveSuggestions as Suggestions, ThreadPrimitiveUnstable_MessageById as Unstable_MessageById, ThreadPrimitiveViewport as Viewport, ThreadPrimitiveViewportFooter as ViewportFooter, ThreadPrimitiveViewportProvider as ViewportProvider };
15
15
  }
16
16
  //#endregion
17
- export { ThreadPrimitiveEmpty as Empty, ThreadPrimitiveIf as If, ThreadPrimitiveMessageByIndex as MessageByIndex, ThreadPrimitiveMessages as Messages, ThreadPrimitiveRoot as Root, ThreadPrimitiveScrollToBottom as ScrollToBottom, ThreadPrimitiveSuggestion as Suggestion, ThreadPrimitiveSuggestionByIndex as SuggestionByIndex, ThreadPrimitiveSuggestions as Suggestions, ThreadPrimitiveViewport as Viewport, ThreadPrimitiveViewportFooter as ViewportFooter, ThreadPrimitiveViewportProvider as ViewportProvider, thread_d_exports };
17
+ export { ThreadPrimitiveEmpty as Empty, ThreadPrimitiveIf as If, ThreadPrimitiveMessageByIndex as MessageByIndex, ThreadPrimitiveMessages as Messages, ThreadPrimitiveRoot as Root, ThreadPrimitiveScrollToBottom as ScrollToBottom, ThreadPrimitiveSuggestion as Suggestion, ThreadPrimitiveSuggestionByIndex as SuggestionByIndex, ThreadPrimitiveSuggestions as Suggestions, ThreadPrimitiveUnstable_MessageById as Unstable_MessageById, ThreadPrimitiveViewport as Viewport, ThreadPrimitiveViewportFooter as ViewportFooter, ThreadPrimitiveViewportProvider as ViewportProvider, thread_d_exports };
18
18
  //# sourceMappingURL=thread.d.ts.map
@@ -5,7 +5,7 @@ import { ThreadPrimitiveEmpty } from "./thread/ThreadEmpty.js";
5
5
  import { ThreadPrimitiveIf } from "./thread/ThreadIf.js";
6
6
  import { ThreadPrimitiveViewport } from "./thread/ThreadViewport.js";
7
7
  import { ThreadPrimitiveViewportFooter } from "./thread/ThreadViewportFooter.js";
8
- import { ThreadPrimitiveMessageByIndex, ThreadPrimitiveMessages } from "./thread/ThreadMessages.js";
8
+ import { ThreadPrimitiveMessageByIndex, ThreadPrimitiveMessages, ThreadPrimitiveUnstable_MessageById } from "./thread/ThreadMessages.js";
9
9
  import { ThreadPrimitiveScrollToBottom } from "./thread/ThreadScrollToBottom.js";
10
10
  import { ThreadPrimitiveSuggestion } from "./thread/ThreadSuggestion.js";
11
11
  import { ThreadPrimitiveSuggestionByIndex, ThreadPrimitiveSuggestions } from "./thread/ThreadSuggestions.js";
@@ -20,11 +20,12 @@ var thread_exports = /* @__PURE__ */ __exportAll({
20
20
  Suggestion: () => ThreadPrimitiveSuggestion,
21
21
  SuggestionByIndex: () => ThreadPrimitiveSuggestionByIndex,
22
22
  Suggestions: () => ThreadPrimitiveSuggestions,
23
+ Unstable_MessageById: () => ThreadPrimitiveUnstable_MessageById,
23
24
  Viewport: () => ThreadPrimitiveViewport,
24
25
  ViewportFooter: () => ThreadPrimitiveViewportFooter,
25
26
  ViewportProvider: () => ThreadPrimitiveViewportProvider
26
27
  });
27
28
  //#endregion
28
- export { ThreadPrimitiveEmpty as Empty, ThreadPrimitiveIf as If, ThreadPrimitiveMessageByIndex as MessageByIndex, ThreadPrimitiveMessages as Messages, ThreadPrimitiveRoot as Root, ThreadPrimitiveScrollToBottom as ScrollToBottom, ThreadPrimitiveSuggestion as Suggestion, ThreadPrimitiveSuggestionByIndex as SuggestionByIndex, ThreadPrimitiveSuggestions as Suggestions, ThreadPrimitiveViewport as Viewport, ThreadPrimitiveViewportFooter as ViewportFooter, ThreadPrimitiveViewportProvider as ViewportProvider, thread_exports };
29
+ export { ThreadPrimitiveEmpty as Empty, ThreadPrimitiveIf as If, ThreadPrimitiveMessageByIndex as MessageByIndex, ThreadPrimitiveMessages as Messages, ThreadPrimitiveRoot as Root, ThreadPrimitiveScrollToBottom as ScrollToBottom, ThreadPrimitiveSuggestion as Suggestion, ThreadPrimitiveSuggestionByIndex as SuggestionByIndex, ThreadPrimitiveSuggestions as Suggestions, ThreadPrimitiveUnstable_MessageById as Unstable_MessageById, ThreadPrimitiveViewport as Viewport, ThreadPrimitiveViewportFooter as ViewportFooter, ThreadPrimitiveViewportProvider as ViewportProvider, thread_exports };
29
30
 
30
31
  //# sourceMappingURL=thread.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"thread.js","names":["ThreadPrimitiveRoot","Root","ThreadPrimitiveEmpty","Empty","ThreadPrimitiveIf","If","ThreadPrimitiveViewport","Viewport","ThreadPrimitiveViewportProvider","ViewportProvider","ThreadPrimitiveViewportFooter","ViewportFooter","ThreadPrimitiveMessages","Messages","ThreadPrimitiveMessageByIndex","MessageByIndex","ThreadPrimitiveScrollToBottom","ScrollToBottom","ThreadPrimitiveSuggestion","Suggestion","ThreadPrimitiveSuggestions","Suggestions","ThreadPrimitiveSuggestionByIndex","SuggestionByIndex"],"sources":["../../src/primitives/thread.ts"],"sourcesContent":["export { ThreadPrimitiveRoot as Root } from \"./thread/ThreadRoot\";\nexport { ThreadPrimitiveEmpty as Empty } from \"./thread/ThreadEmpty\";\nexport { ThreadPrimitiveIf as If } from \"./thread/ThreadIf\";\nexport { ThreadPrimitiveViewport as Viewport } from \"./thread/ThreadViewport\";\nexport { ThreadPrimitiveViewportProvider as ViewportProvider } from \"../context/providers/ThreadViewportProvider\";\nexport { ThreadPrimitiveViewportFooter as ViewportFooter } from \"./thread/ThreadViewportFooter\";\nexport { ThreadPrimitiveMessages as Messages } from \"./thread/ThreadMessages\";\nexport { ThreadPrimitiveMessageByIndex as MessageByIndex } from \"./thread/ThreadMessages\";\nexport { ThreadPrimitiveScrollToBottom as ScrollToBottom } from \"./thread/ThreadScrollToBottom\";\nexport { ThreadPrimitiveSuggestion as Suggestion } from \"./thread/ThreadSuggestion\";\nexport {\n ThreadPrimitiveSuggestions as Suggestions,\n ThreadPrimitiveSuggestionByIndex as SuggestionByIndex,\n} from \"./thread/ThreadSuggestions\";\n"],"mappings":""}
1
+ {"version":3,"file":"thread.js","names":["ThreadPrimitiveRoot","Root","ThreadPrimitiveEmpty","Empty","ThreadPrimitiveIf","If","ThreadPrimitiveViewport","Viewport","ThreadPrimitiveViewportProvider","ViewportProvider","ThreadPrimitiveViewportFooter","ViewportFooter","ThreadPrimitiveMessages","Messages","ThreadPrimitiveMessageByIndex","MessageByIndex","ThreadPrimitiveUnstable_MessageById","Unstable_MessageById","ThreadPrimitiveScrollToBottom","ScrollToBottom","ThreadPrimitiveSuggestion","Suggestion","ThreadPrimitiveSuggestions","Suggestions","ThreadPrimitiveSuggestionByIndex","SuggestionByIndex"],"sources":["../../src/primitives/thread.ts"],"sourcesContent":["export { ThreadPrimitiveRoot as Root } from \"./thread/ThreadRoot\";\nexport { ThreadPrimitiveEmpty as Empty } from \"./thread/ThreadEmpty\";\nexport { ThreadPrimitiveIf as If } from \"./thread/ThreadIf\";\nexport { ThreadPrimitiveViewport as Viewport } from \"./thread/ThreadViewport\";\nexport { ThreadPrimitiveViewportProvider as ViewportProvider } from \"../context/providers/ThreadViewportProvider\";\nexport { ThreadPrimitiveViewportFooter as ViewportFooter } from \"./thread/ThreadViewportFooter\";\nexport { ThreadPrimitiveMessages as Messages } from \"./thread/ThreadMessages\";\nexport { ThreadPrimitiveMessageByIndex as MessageByIndex } from \"./thread/ThreadMessages\";\nexport { ThreadPrimitiveUnstable_MessageById as Unstable_MessageById } from \"./thread/ThreadMessages\";\nexport { ThreadPrimitiveScrollToBottom as ScrollToBottom } from \"./thread/ThreadScrollToBottom\";\nexport { ThreadPrimitiveSuggestion as Suggestion } from \"./thread/ThreadSuggestion\";\nexport {\n ThreadPrimitiveSuggestions as Suggestions,\n ThreadPrimitiveSuggestionByIndex as SuggestionByIndex,\n} from \"./thread/ThreadSuggestions\";\n"],"mappings":""}
@@ -0,0 +1,84 @@
1
+ import { TriggerPopoverAriaProps } from "../primitives/composer/useComposerInputState.js";
2
+ import { ComposerSendOptions } from "@assistant-ui/core/store";
3
+
4
+ //#region src/unstable/useComposerInput.d.ts
5
+ type Unstable_UseComposerInputOptions = {
6
+ /**
7
+ * Disables the input in addition to the composer's own disabled sources
8
+ * (thread disabled, active dictation). When disabled, `isDisabled` is `true`
9
+ * and `canSend` is `false`.
10
+ */
11
+ disabled?: boolean | undefined;
12
+ };
13
+ type Unstable_ComposerInput = {
14
+ /** Current composer text, or `""` when the composer is not editing. */value: string;
15
+ /**
16
+ * Writes `text` into the composer, mirroring `ComposerPrimitive.Input`:
17
+ * a no-op unless the composer is editing, committed via `flushTapSync` so the
18
+ * controlled value stays in sync within the same tick.
19
+ */
20
+ setText(text: string): void;
21
+ /**
22
+ * Sends the current message when `canSend` is `true`; otherwise a no-op.
23
+ * Accepts the same options as the composer send action.
24
+ */
25
+ send(options?: ComposerSendOptions): void;
26
+ /**
27
+ * Whether the input is disabled, combining the `disabled` option with the
28
+ * composer's own disabled sources (thread disabled, active dictation).
29
+ */
30
+ isDisabled: boolean;
31
+ /**
32
+ * Whether a send is currently available. Matches `ComposerPrimitive.Send`
33
+ * gating (non-empty editing composer, not running without queue support) and
34
+ * is additionally `false` while `isDisabled`.
35
+ */
36
+ canSend: boolean;
37
+ };
38
+ /**
39
+ * @deprecated Under active development and might change without notice.
40
+ *
41
+ * Headless bridge to the composer's text value and send action, for building a
42
+ * custom composer input without `ComposerPrimitive.Input`. It is a thin bridge,
43
+ * not a second input: it does not own keyboard behavior, autosize, IME or
44
+ * contentEditable sync, paste/drop attachments, focus management, or rich-text
45
+ * state. Spread `unstable_useTriggerPopoverAriaProps()` onto your element for
46
+ * trigger-popover combobox semantics.
47
+ *
48
+ * @example
49
+ * ```tsx
50
+ * const { value, setText, send, isDisabled, canSend } = unstable_useComposerInput();
51
+ * <textarea
52
+ * value={value}
53
+ * disabled={isDisabled}
54
+ * onChange={(e) => setText(e.target.value)}
55
+ * onKeyDown={(e) => {
56
+ * if (e.key === "Enter" && !e.shiftKey && canSend) {
57
+ * e.preventDefault();
58
+ * send();
59
+ * }
60
+ * }}
61
+ * />
62
+ * ```
63
+ */
64
+ declare function unstable_useComposerInput(options?: Unstable_UseComposerInputOptions): Unstable_ComposerInput;
65
+ type Unstable_TriggerPopoverAriaProps = TriggerPopoverAriaProps;
66
+ /**
67
+ * @deprecated Under active development and might change without notice.
68
+ *
69
+ * ARIA combobox attributes for the focused element (typically the composer
70
+ * input) describing the open trigger popover, per the WAI-ARIA editable
71
+ * combobox pattern. Returns an empty object outside a `TriggerPopoverRoot` or
72
+ * when no popover is open. Spread these last so they take precedence over any
73
+ * matching ARIA props you set yourself, mirroring `ComposerPrimitive.Input`.
74
+ *
75
+ * @example
76
+ * ```tsx
77
+ * const aria = unstable_useTriggerPopoverAriaProps();
78
+ * <textarea {...aria} />
79
+ * ```
80
+ */
81
+ declare function unstable_useTriggerPopoverAriaProps(): Unstable_TriggerPopoverAriaProps;
82
+ //#endregion
83
+ export { Unstable_ComposerInput, Unstable_TriggerPopoverAriaProps, Unstable_UseComposerInputOptions, unstable_useComposerInput, unstable_useTriggerPopoverAriaProps };
84
+ //# sourceMappingURL=useComposerInput.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useComposerInput.d.ts","names":[],"sources":["../../src/unstable/useComposerInput.ts"],"mappings":";;;;KAcY,gCAAA;;AAAZ;;;;EAME,QAAQ;AAAA;AAAA,KAGE,sBAAA;yEAEV,KAAA;EAAA;;;;;EAMA,OAAA,CAAQ,IAAA;EAUR;;;AAMO;EAXP,IAAA,CAAK,OAAA,GAAU,mBAAmB;EAwCK;;;;EAnCvC,UAAA;EAqCC;;AAAsB;AA4BzB;;EA3DE,OAAA;AAAA;AA2DoE;AAiBtE;;;;AAAuF;;;;;;;;;;;;;;;;;;;;;AAjBjB,iBA9BtD,yBAAA,CACd,OAAA,GAAU,gCAAA,GACT,sBAAsB;AAAA,KA4Bb,gCAAA,GAAmC,uBAAuB;;;;;;;;;;;;;;;;iBAiBtD,mCAAA,IAAuC,gCAAgC"}
@@ -0,0 +1,78 @@
1
+ "use client";
2
+ import { useComposerInputDisabled, useComposerInputValue, useTriggerPopoverAriaProps } from "../primitives/composer/useComposerInputState.js";
3
+ import { useAui } from "@assistant-ui/store";
4
+ import { useComposerSend } from "@assistant-ui/core/react";
5
+ import { useCallback } from "@assistant-ui/tap/react-shim";
6
+ import { flushTapSync } from "@assistant-ui/tap";
7
+ //#region src/unstable/useComposerInput.ts
8
+ /**
9
+ * @deprecated Under active development and might change without notice.
10
+ *
11
+ * Headless bridge to the composer's text value and send action, for building a
12
+ * custom composer input without `ComposerPrimitive.Input`. It is a thin bridge,
13
+ * not a second input: it does not own keyboard behavior, autosize, IME or
14
+ * contentEditable sync, paste/drop attachments, focus management, or rich-text
15
+ * state. Spread `unstable_useTriggerPopoverAriaProps()` onto your element for
16
+ * trigger-popover combobox semantics.
17
+ *
18
+ * @example
19
+ * ```tsx
20
+ * const { value, setText, send, isDisabled, canSend } = unstable_useComposerInput();
21
+ * <textarea
22
+ * value={value}
23
+ * disabled={isDisabled}
24
+ * onChange={(e) => setText(e.target.value)}
25
+ * onKeyDown={(e) => {
26
+ * if (e.key === "Enter" && !e.shiftKey && canSend) {
27
+ * e.preventDefault();
28
+ * send();
29
+ * }
30
+ * }}
31
+ * />
32
+ * ```
33
+ */
34
+ function unstable_useComposerInput(options) {
35
+ const aui = useAui();
36
+ const value = useComposerInputValue();
37
+ const isDisabled = useComposerInputDisabled(options?.disabled);
38
+ const setText = useCallback((text) => {
39
+ if (!aui.composer().getState().isEditing) return;
40
+ flushTapSync(() => {
41
+ aui.composer().setText(text);
42
+ });
43
+ }, [aui]);
44
+ const { send: rawSend, disabled: sendDisabled } = useComposerSend();
45
+ const canSend = !sendDisabled && !isDisabled;
46
+ return {
47
+ value,
48
+ setText,
49
+ send: useCallback((sendOptions) => {
50
+ if (!canSend) return;
51
+ rawSend(sendOptions);
52
+ }, [canSend, rawSend]),
53
+ isDisabled,
54
+ canSend
55
+ };
56
+ }
57
+ /**
58
+ * @deprecated Under active development and might change without notice.
59
+ *
60
+ * ARIA combobox attributes for the focused element (typically the composer
61
+ * input) describing the open trigger popover, per the WAI-ARIA editable
62
+ * combobox pattern. Returns an empty object outside a `TriggerPopoverRoot` or
63
+ * when no popover is open. Spread these last so they take precedence over any
64
+ * matching ARIA props you set yourself, mirroring `ComposerPrimitive.Input`.
65
+ *
66
+ * @example
67
+ * ```tsx
68
+ * const aria = unstable_useTriggerPopoverAriaProps();
69
+ * <textarea {...aria} />
70
+ * ```
71
+ */
72
+ function unstable_useTriggerPopoverAriaProps() {
73
+ return useTriggerPopoverAriaProps();
74
+ }
75
+ //#endregion
76
+ export { unstable_useComposerInput, unstable_useTriggerPopoverAriaProps };
77
+
78
+ //# sourceMappingURL=useComposerInput.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useComposerInput.js","names":["useCallback","useAui","flushTapSync","useComposerSend","ComposerSendOptions","TriggerPopoverAriaProps","useComposerInputDisabled","useComposerInputValue","useTriggerPopoverAriaProps","Unstable_UseComposerInputOptions","disabled","Unstable_ComposerInput","value","setText","text","send","options","isDisabled","canSend","unstable_useComposerInput","aui","composer","getState","isEditing","rawSend","sendDisabled","sendOptions","Unstable_TriggerPopoverAriaProps","unstable_useTriggerPopoverAriaProps"],"sources":["../../src/unstable/useComposerInput.ts"],"sourcesContent":["\"use client\";\n\nimport { useCallback } from \"react\";\nimport { useAui } from \"@assistant-ui/store\";\nimport { flushTapSync } from \"@assistant-ui/tap\";\nimport { useComposerSend } from \"@assistant-ui/core/react\";\nimport type { ComposerSendOptions } from \"@assistant-ui/core/store\";\nimport {\n type TriggerPopoverAriaProps,\n useComposerInputDisabled,\n useComposerInputValue,\n useTriggerPopoverAriaProps,\n} from \"../primitives/composer/useComposerInputState\";\n\nexport type Unstable_UseComposerInputOptions = {\n /**\n * Disables the input in addition to the composer's own disabled sources\n * (thread disabled, active dictation). When disabled, `isDisabled` is `true`\n * and `canSend` is `false`.\n */\n disabled?: boolean | undefined;\n};\n\nexport type Unstable_ComposerInput = {\n /** Current composer text, or `\"\"` when the composer is not editing. */\n value: string;\n /**\n * Writes `text` into the composer, mirroring `ComposerPrimitive.Input`:\n * a no-op unless the composer is editing, committed via `flushTapSync` so the\n * controlled value stays in sync within the same tick.\n */\n setText(text: string): void;\n /**\n * Sends the current message when `canSend` is `true`; otherwise a no-op.\n * Accepts the same options as the composer send action.\n */\n send(options?: ComposerSendOptions): void;\n /**\n * Whether the input is disabled, combining the `disabled` option with the\n * composer's own disabled sources (thread disabled, active dictation).\n */\n isDisabled: boolean;\n /**\n * Whether a send is currently available. Matches `ComposerPrimitive.Send`\n * gating (non-empty editing composer, not running without queue support) and\n * is additionally `false` while `isDisabled`.\n */\n canSend: boolean;\n};\n\n/**\n * @deprecated Under active development and might change without notice.\n *\n * Headless bridge to the composer's text value and send action, for building a\n * custom composer input without `ComposerPrimitive.Input`. It is a thin bridge,\n * not a second input: it does not own keyboard behavior, autosize, IME or\n * contentEditable sync, paste/drop attachments, focus management, or rich-text\n * state. Spread `unstable_useTriggerPopoverAriaProps()` onto your element for\n * trigger-popover combobox semantics.\n *\n * @example\n * ```tsx\n * const { value, setText, send, isDisabled, canSend } = unstable_useComposerInput();\n * <textarea\n * value={value}\n * disabled={isDisabled}\n * onChange={(e) => setText(e.target.value)}\n * onKeyDown={(e) => {\n * if (e.key === \"Enter\" && !e.shiftKey && canSend) {\n * e.preventDefault();\n * send();\n * }\n * }}\n * />\n * ```\n */\nexport function unstable_useComposerInput(\n options?: Unstable_UseComposerInputOptions,\n): Unstable_ComposerInput {\n const aui = useAui();\n const value = useComposerInputValue();\n const isDisabled = useComposerInputDisabled(options?.disabled);\n\n const setText = useCallback(\n (text: string) => {\n if (!aui.composer().getState().isEditing) return;\n flushTapSync(() => {\n aui.composer().setText(text);\n });\n },\n [aui],\n );\n\n const { send: rawSend, disabled: sendDisabled } = useComposerSend();\n const canSend = !sendDisabled && !isDisabled;\n const send = useCallback(\n (sendOptions?: ComposerSendOptions) => {\n if (!canSend) return;\n rawSend(sendOptions);\n },\n [canSend, rawSend],\n );\n\n return { value, setText, send, isDisabled, canSend };\n}\n\nexport type Unstable_TriggerPopoverAriaProps = TriggerPopoverAriaProps;\n\n/**\n * @deprecated Under active development and might change without notice.\n *\n * ARIA combobox attributes for the focused element (typically the composer\n * input) describing the open trigger popover, per the WAI-ARIA editable\n * combobox pattern. Returns an empty object outside a `TriggerPopoverRoot` or\n * when no popover is open. Spread these last so they take precedence over any\n * matching ARIA props you set yourself, mirroring `ComposerPrimitive.Input`.\n *\n * @example\n * ```tsx\n * const aria = unstable_useTriggerPopoverAriaProps();\n * <textarea {...aria} />\n * ```\n */\nexport function unstable_useTriggerPopoverAriaProps(): Unstable_TriggerPopoverAriaProps {\n return useTriggerPopoverAriaProps();\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA4EA,SAAgBmB,0BACdH,SACwB;CACxB,MAAMI,MAAMnB,OAAO;CACnB,MAAMW,QAAQL,sBAAsB;CACpC,MAAMU,aAAaX,yBAAyBU,SAASN,QAAQ;CAE7D,MAAMG,UAAUb,aACbc,SAAiB;EAChB,IAAI,CAACM,IAAIC,SAAS,CAAC,CAACC,SAAS,CAAC,CAACC,WAAW;EAC1CrB,mBAAmB;GACjBkB,IAAIC,SAAS,CAAC,CAACR,QAAQC,IAAI;EAC7B,CAAC;CACH,GACA,CAACM,GAAG,CACN;CAEA,MAAM,EAAEL,MAAMS,SAASd,UAAUe,iBAAiBtB,gBAAgB;CAClE,MAAMe,UAAU,CAACO,gBAAgB,CAACR;CASlC,OAAO;EAAEL;EAAOC;EAASE,MARZf,aACV0B,gBAAsC;GACrC,IAAI,CAACR,SAAS;GACdM,QAAQE,WAAW;EACrB,GACA,CAACR,SAASM,OAAO,CAGMT;EAAME;EAAYC;CAAQ;AACrD;;;;;;;;;;;;;;;;AAmBA,SAAgBU,sCAAwE;CACtF,OAAOpB,2BAA2B;AACpC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@assistant-ui/react",
3
- "version": "0.14.22",
3
+ "version": "0.14.24",
4
4
  "description": "Open-source TypeScript/React library for building production-grade AI chat experiences",
5
5
  "keywords": [
6
6
  "ai",
@@ -55,19 +55,19 @@
55
55
  ],
56
56
  "sideEffects": false,
57
57
  "dependencies": {
58
- "@assistant-ui/core": "^0.2.17",
59
- "@assistant-ui/store": "^0.2.18",
60
- "@assistant-ui/tap": "^0.9.2",
58
+ "@assistant-ui/core": "^0.2.19",
59
+ "@assistant-ui/store": "^0.2.19",
60
+ "@assistant-ui/tap": "^0.9.3",
61
61
  "@radix-ui/primitive": "^1.1.4",
62
62
  "@radix-ui/react-compose-refs": "^1.1.3",
63
63
  "@radix-ui/react-context": "^1.1.4",
64
- "@radix-ui/react-primitive": "^2.1.5",
64
+ "@radix-ui/react-primitive": "^2.1.6",
65
65
  "@radix-ui/react-use-callback-ref": "^1.1.2",
66
66
  "@radix-ui/react-use-escape-keydown": "^1.1.2",
67
- "assistant-cloud": "^0.1.33",
68
- "assistant-stream": "^0.3.23",
69
- "nanoid": "^5.1.11",
70
- "radix-ui": "^1.5.0",
67
+ "assistant-cloud": "^0.1.34",
68
+ "assistant-stream": "^0.3.24",
69
+ "nanoid": "^5.1.15",
70
+ "radix-ui": "^1.6.0",
71
71
  "react-textarea-autosize": "^8.5.9",
72
72
  "safe-content-frame": "^0.0.21",
73
73
  "zod": "^4.4.3",
@@ -90,15 +90,15 @@
90
90
  "devDependencies": {
91
91
  "@testing-library/react": "^16.3.2",
92
92
  "@types/json-schema": "^7.0.15",
93
- "@types/node": "^25.9.3",
93
+ "@types/node": "^26.0.0",
94
94
  "@types/react": "^19.2.17",
95
95
  "@types/react-dom": "^19.2.3",
96
96
  "jsdom": "^29.1.1",
97
97
  "react": "^19.2.7",
98
98
  "react-dom": "^19.2.7",
99
- "vitest": "^4.1.8",
100
- "@assistant-ui/vite": "0.0.5",
101
- "@assistant-ui/x-buildutils": "0.0.15"
99
+ "vitest": "^4.1.9",
100
+ "@assistant-ui/vite": "0.0.6",
101
+ "@assistant-ui/x-buildutils": "0.0.16"
102
102
  },
103
103
  "publishConfig": {
104
104
  "access": "public",
package/src/index.ts CHANGED
@@ -136,6 +136,7 @@ export type {
136
136
  ExternalStoreSharedOptions,
137
137
  ExternalStoreThreadListAdapter,
138
138
  ExternalStoreThreadData,
139
+ ExternalStoreBranchChange,
139
140
  } from "@assistant-ui/core";
140
141
  export {
141
142
  createMessageQueue,
@@ -215,6 +216,26 @@ export {
215
216
  useAssistantInteractable,
216
217
  type AssistantInteractableProps,
217
218
  useInteractableState,
219
+ unstable_Interactables,
220
+ unstable_useInteractable,
221
+ type Unstable_InteractableConfig,
222
+ type Unstable_InferInteractableState,
223
+ type Unstable_InteractableVersionInfo,
224
+ unstable_useInteractableState,
225
+ unstable_useInteractableVersions,
226
+ unstable_interactableTool,
227
+ type Unstable_InteractableToolConfig,
228
+ type Unstable_InteractableToolRenderProps,
229
+ type Unstable_InteractableStateSchema,
230
+ type Unstable_InteractablesState,
231
+ type Unstable_InteractableDefinition,
232
+ type Unstable_InteractableRegistration,
233
+ type Unstable_InteractablesMethods,
234
+ type Unstable_InteractablePersistedState,
235
+ type Unstable_InteractablePersistenceAdapter,
236
+ type Unstable_InteractablePersistenceStatus,
237
+ type Unstable_InteractablesClientSchema,
238
+ type Unstable_InteractablesConfig,
218
239
  useToolArgsStatus,
219
240
  type ToolArgsStatus,
220
241
  } from "@assistant-ui/core/react";
@@ -228,6 +249,14 @@ export type {
228
249
 
229
250
  export { mergeModelContexts } from "@assistant-ui/core";
230
251
 
252
+ export {
253
+ unstable_getInteractableSnapshots,
254
+ unstable_formatInteractableSnapshot,
255
+ unstable_getInteractableVersions,
256
+ type Unstable_InteractableSnapshotEntry,
257
+ type Unstable_InteractableVersion,
258
+ } from "@assistant-ui/core";
259
+
231
260
  export type { Tool } from "assistant-stream";
232
261
 
233
262
  export { tool } from "@assistant-ui/core";
@@ -280,6 +309,7 @@ export * as ThreadListItemMorePrimitive from "./primitives/threadListItemMore";
280
309
  export * as SelectionToolbarPrimitive from "./primitives/selectionToolbar";
281
310
 
282
311
  export { groupPartByType, type GroupByContext } from "@assistant-ui/core/react";
312
+ export { unstable_useThreadMessageIds } from "@assistant-ui/core/react";
283
313
  export { useMessagePartText } from "./primitives/messagePart/useMessagePartText";
284
314
  export { useMessagePartReasoning } from "./primitives/messagePart/useMessagePartReasoning";
285
315
  export { useMessagePartSource } from "./primitives/messagePart/useMessagePartSource";
@@ -447,6 +477,15 @@ export {
447
477
  type Unstable_ComposerInputHistory,
448
478
  } from "./unstable/useComposerInputHistory";
449
479
 
480
+ // Unstable - headless composer input bridge (value/send without ComposerPrimitive.Input)
481
+ export {
482
+ unstable_useComposerInput,
483
+ unstable_useTriggerPopoverAriaProps,
484
+ type Unstable_UseComposerInputOptions,
485
+ type Unstable_ComposerInput,
486
+ type Unstable_TriggerPopoverAriaProps,
487
+ } from "./unstable/useComposerInput";
488
+
450
489
  export type { Assistant } from "./augmentations";
451
490
 
452
491
  // --- mcp-apps ---
@@ -19,14 +19,17 @@ export const useCommandQueue = (opts: { onQueue: () => void }) => {
19
19
  const [, rerender] = useState(0);
20
20
  const queueStateRef = useRef<CommandQueueState>(createInitialQueueState());
21
21
 
22
- const enqueue = (command: AssistantTransportCommand) => {
22
+ const enqueue = (
23
+ command: AssistantTransportCommand,
24
+ options?: { schedule?: boolean },
25
+ ) => {
23
26
  queueStateRef.current = {
24
27
  queued: [...queueStateRef.current.queued, command],
25
28
  inTransit: queueStateRef.current.inTransit,
26
29
  };
27
30
  rerender((prev) => prev + 1);
28
31
 
29
- onQueueRef.current();
32
+ if (options?.schedule !== false) onQueueRef.current();
30
33
  };
31
34
 
32
35
  const flush = (): QueuedCommand[] => {
@@ -99,6 +99,28 @@ describe("assistant transport scheduling contracts", () => {
99
99
  expect(result.current.runBatchesRef.current[1]).toHaveLength(1);
100
100
  });
101
101
 
102
+ it("can enqueue without scheduling until a run is started", async () => {
103
+ const { result } = renderHook(() => useTransportSchedulingHarness());
104
+
105
+ act(() => {
106
+ result.current.commandQueue.enqueue(createMessageCommand("staged"), {
107
+ schedule: false,
108
+ });
109
+ });
110
+
111
+ await Promise.resolve();
112
+ expect(result.current.runBatchesRef.current).toHaveLength(0);
113
+
114
+ act(() => {
115
+ result.current.runManager.schedule();
116
+ });
117
+
118
+ await waitFor(() => {
119
+ expect(result.current.runBatchesRef.current).toHaveLength(1);
120
+ });
121
+ expect(result.current.runBatchesRef.current[0]).toHaveLength(1);
122
+ });
123
+
102
124
  it("cancel returns combined in-flight and queued commands", async () => {
103
125
  const onCancel = vi.fn();
104
126
  const { result } = renderHook(() =>
@@ -316,13 +316,23 @@ const useAssistantTransportThreadRuntime = <T>(
316
316
  onNew: async (message: AppendMessage): Promise<void> => {
317
317
  parentIdRef.current = message.parentId;
318
318
  const command = convertAppendMessageToCommand(message);
319
- commandQueue.enqueue(command);
319
+ commandQueue.enqueue(command, {
320
+ schedule: message.startRun ?? message.role === "user",
321
+ });
320
322
  },
321
323
  ...(options.capabilities?.edit && {
322
324
  onEdit: async (message: AppendMessage): Promise<void> => {
323
325
  parentIdRef.current = message.parentId;
324
326
  const command = convertAppendMessageToCommand(message);
325
- commandQueue.enqueue(command);
327
+ commandQueue.enqueue(command, {
328
+ schedule: message.startRun ?? message.role === "user",
329
+ });
330
+ },
331
+ }),
332
+ ...(commandQueue.state.queued.length > 0 && {
333
+ onReload: async (parentId: string | null) => {
334
+ parentIdRef.current = parentId;
335
+ runManager.schedule();
326
336
  },
327
337
  }),
328
338
  onCancel: async () => {
@@ -21,10 +21,14 @@ import TextareaAutosize, {
21
21
  import { useEscapeKeydown } from "@radix-ui/react-use-escape-keydown";
22
22
  import { useOnScrollToBottom } from "../../utils/hooks/useOnScrollToBottom";
23
23
  import { useMediaQuery } from "../../utils/hooks/useMediaQuery";
24
- import { useAuiState, useAui } from "@assistant-ui/store";
24
+ import { useAui } from "@assistant-ui/store";
25
25
  import { flushTapSync } from "@assistant-ui/tap";
26
26
  import { useComposerInputPluginRegistryOptional } from "./ComposerInputPluginContext";
27
- import { useTriggerPopoverActiveAriaOptional } from "./trigger/TriggerPopoverRootContext";
27
+ import {
28
+ useComposerInputDisabled,
29
+ useComposerInputValue,
30
+ useTriggerPopoverAriaProps,
31
+ } from "./useComposerInputState";
28
32
 
29
33
  const TOUCH_PRIMARY_QUERY = "(pointer: coarse) and (not (any-pointer: fine))";
30
34
 
@@ -167,7 +171,6 @@ export const ComposerPrimitiveInput = forwardRef<
167
171
  ) => {
168
172
  const aui = useAui();
169
173
  const pluginRegistry = useComposerInputPluginRegistryOptional();
170
- const activeAria = useTriggerPopoverActiveAriaOptional();
171
174
 
172
175
  const declaredSubmitMode =
173
176
  submitMode ?? (submitOnEnter === false ? "none" : "enter");
@@ -181,15 +184,8 @@ export const ComposerPrimitiveInput = forwardRef<
181
184
  ? "none"
182
185
  : declaredSubmitMode;
183
186
 
184
- const value = useAuiState((s) => {
185
- if (!s.composer.isEditing) return "";
186
- return s.composer.text;
187
- });
188
-
189
- const isDisabled =
190
- useAuiState(
191
- (s) => s.thread.isDisabled || s.composer.dictation?.inputDisabled,
192
- ) || disabledProp;
187
+ const value = useComposerInputValue();
188
+ const isDisabled = useComposerInputDisabled(disabledProp);
193
189
  const textareaRef = useRef<HTMLTextAreaElement>(null);
194
190
  const ref = useComposedRefs(forwardedRef, textareaRef);
195
191
  // suppress text/cursor broadcasts during IME composition
@@ -322,14 +318,7 @@ export const ComposerPrimitiveInput = forwardRef<
322
318
  return aui.on("threadListItem.switchedTo", focus);
323
319
  }, [unstable_focusOnThreadSwitched, focus, aui]);
324
320
 
325
- const ariaComboboxProps = activeAria
326
- ? {
327
- "aria-controls": activeAria.popoverId,
328
- "aria-expanded": true as const,
329
- "aria-haspopup": "listbox" as const,
330
- "aria-activedescendant": activeAria.highlightedItemId,
331
- }
332
- : {};
321
+ const ariaComboboxProps = useTriggerPopoverAriaProps();
333
322
 
334
323
  const inputProps = {
335
324
  name: "input" as const,
@@ -1,5 +1,5 @@
1
1
  import { describe, it, expect, vi } from "vitest";
2
- import { createTapRoot, useResource } from "@assistant-ui/tap";
2
+ import { createTapRoot, flushTapSync, useResource } from "@assistant-ui/tap";
3
3
  import type {
4
4
  Unstable_TriggerCategory,
5
5
  Unstable_TriggerItem,
@@ -23,8 +23,6 @@ const makeKeyEvent = (key: string, shiftKey = false) => ({
23
23
  preventDefault: vi.fn(),
24
24
  });
25
25
 
26
- const tick = () => new Promise<void>((resolve) => setTimeout(resolve, 0));
27
-
28
26
  const render = (
29
27
  overrides: Partial<Parameters<typeof TriggerKeyboardResource>[0]> = {},
30
28
  ) => {
@@ -106,55 +104,53 @@ describe("TriggerKeyboardResource", () => {
106
104
  expect(props.selectItem).not.toHaveBeenCalled();
107
105
  });
108
106
 
109
- it("moves the highlight forward on ArrowDown", async () => {
107
+ it("moves the highlight forward on ArrowDown", () => {
110
108
  const { sub } = render();
111
109
 
112
- const consumed = sub.getValue().handleKeyDown(makeKeyEvent("ArrowDown"));
113
- await tick();
110
+ const consumed = flushTapSync(() =>
111
+ sub.getValue().handleKeyDown(makeKeyEvent("ArrowDown")),
112
+ );
114
113
 
115
114
  expect(consumed).toBe(true);
116
115
  expect(sub.getValue().highlightedIndex).toBe(1);
117
116
  });
118
117
 
119
- it("wraps the highlight to the top on ArrowDown past the last entry", async () => {
118
+ it("wraps the highlight to the top on ArrowDown past the last entry", () => {
120
119
  const { sub } = render();
121
120
  const handle = sub.getValue().handleKeyDown;
122
121
 
123
- handle(makeKeyEvent("ArrowDown"));
124
- handle(makeKeyEvent("ArrowDown"));
125
- handle(makeKeyEvent("ArrowDown"));
126
- await tick();
122
+ flushTapSync(() => {
123
+ handle(makeKeyEvent("ArrowDown"));
124
+ handle(makeKeyEvent("ArrowDown"));
125
+ handle(makeKeyEvent("ArrowDown"));
126
+ });
127
127
 
128
128
  expect(sub.getValue().highlightedIndex).toBe(0);
129
129
  });
130
130
 
131
- it("moves the highlight backward on ArrowUp", async () => {
131
+ it("moves the highlight backward on ArrowUp", () => {
132
132
  const { sub } = render();
133
133
  const handle = sub.getValue().handleKeyDown;
134
134
 
135
- handle(makeKeyEvent("ArrowDown"));
136
- await tick();
137
- handle(makeKeyEvent("ArrowUp"));
138
- await tick();
135
+ flushTapSync(() => handle(makeKeyEvent("ArrowDown")));
136
+ flushTapSync(() => handle(makeKeyEvent("ArrowUp")));
139
137
 
140
138
  expect(sub.getValue().highlightedIndex).toBe(0);
141
139
  });
142
140
 
143
- it("wraps the highlight to the bottom on ArrowUp from the first entry", async () => {
141
+ it("wraps the highlight to the bottom on ArrowUp from the first entry", () => {
144
142
  const { sub } = render();
145
143
 
146
- sub.getValue().handleKeyDown(makeKeyEvent("ArrowUp"));
147
- await tick();
144
+ flushTapSync(() => sub.getValue().handleKeyDown(makeKeyEvent("ArrowUp")));
148
145
 
149
146
  expect(sub.getValue().highlightedIndex).toBe(2);
150
147
  });
151
148
 
152
- it("keeps the highlight at 0 on ArrowDown when navigableList is empty", async () => {
149
+ it("keeps the highlight at 0 on ArrowDown when navigableList is empty", () => {
153
150
  const { sub } = render({ navigableList: [] });
154
151
  const e = makeKeyEvent("ArrowDown");
155
152
 
156
- const consumed = sub.getValue().handleKeyDown(e);
157
- await tick();
153
+ const consumed = flushTapSync(() => sub.getValue().handleKeyDown(e));
158
154
 
159
155
  expect(consumed).toBe(true);
160
156
  expect(e.preventDefault).toHaveBeenCalled();
@@ -0,0 +1,34 @@
1
+ "use client";
2
+
3
+ import { useAuiState } from "@assistant-ui/store";
4
+ import { useTriggerPopoverActiveAriaOptional } from "./trigger/TriggerPopoverRootContext";
5
+
6
+ export type TriggerPopoverAriaProps = {
7
+ "aria-controls"?: string;
8
+ "aria-expanded"?: true;
9
+ "aria-haspopup"?: "listbox";
10
+ "aria-activedescendant"?: string | undefined;
11
+ };
12
+
13
+ export function useComposerInputValue() {
14
+ return useAuiState((s) => (s.composer.isEditing ? s.composer.text : ""));
15
+ }
16
+
17
+ export function useComposerInputDisabled(disabled?: boolean | undefined) {
18
+ const composerDisabled = useAuiState(
19
+ (s) => s.thread.isDisabled || s.composer.dictation?.inputDisabled,
20
+ );
21
+ return Boolean(composerDisabled) || Boolean(disabled);
22
+ }
23
+
24
+ export function useTriggerPopoverAriaProps(): TriggerPopoverAriaProps {
25
+ const activeAria = useTriggerPopoverActiveAriaOptional();
26
+ if (!activeAria) return {};
27
+
28
+ return {
29
+ "aria-controls": activeAria.popoverId,
30
+ "aria-expanded": true,
31
+ "aria-haspopup": "listbox",
32
+ "aria-activedescendant": activeAria.highlightedItemId,
33
+ };
34
+ }
@@ -4,4 +4,5 @@ export {
4
4
  ThreadPrimitiveMessages,
5
5
  ThreadPrimitiveMessagesImpl,
6
6
  ThreadPrimitiveMessageByIndex,
7
+ ThreadPrimitiveUnstable_MessageById,
7
8
  } from "@assistant-ui/core/react";
@@ -6,6 +6,7 @@ export { ThreadPrimitiveViewportProvider as ViewportProvider } from "../context/
6
6
  export { ThreadPrimitiveViewportFooter as ViewportFooter } from "./thread/ThreadViewportFooter";
7
7
  export { ThreadPrimitiveMessages as Messages } from "./thread/ThreadMessages";
8
8
  export { ThreadPrimitiveMessageByIndex as MessageByIndex } from "./thread/ThreadMessages";
9
+ export { ThreadPrimitiveUnstable_MessageById as Unstable_MessageById } from "./thread/ThreadMessages";
9
10
  export { ThreadPrimitiveScrollToBottom as ScrollToBottom } from "./thread/ThreadScrollToBottom";
10
11
  export { ThreadPrimitiveSuggestion as Suggestion } from "./thread/ThreadSuggestion";
11
12
  export {