@assistant-ui/react 0.15.4 → 0.15.5

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 (58) hide show
  1. package/dist/client/ExternalThread.d.ts.map +1 -1
  2. package/dist/client/ExternalThread.js +383 -368
  3. package/dist/client/ExternalThread.js.map +1 -1
  4. package/dist/context/providers/MessageProvider.js +15 -34
  5. package/dist/context/providers/MessageProvider.js.map +1 -1
  6. package/dist/index.d.ts +3 -3
  7. package/dist/index.js +2 -2
  8. package/dist/legacy-runtime/AssistantRuntimeProvider.d.ts +6 -1
  9. package/dist/legacy-runtime/AssistantRuntimeProvider.d.ts.map +1 -1
  10. package/dist/legacy-runtime/AssistantRuntimeProvider.js +10 -8
  11. package/dist/legacy-runtime/AssistantRuntimeProvider.js.map +1 -1
  12. package/dist/legacy-runtime/runtime-cores/assistant-transport/runManager.d.ts.map +1 -1
  13. package/dist/legacy-runtime/runtime-cores/assistant-transport/runManager.js +8 -1
  14. package/dist/legacy-runtime/runtime-cores/assistant-transport/runManager.js.map +1 -1
  15. package/dist/legacy-runtime/runtime-cores/assistant-transport/types.d.ts +5 -1
  16. package/dist/legacy-runtime/runtime-cores/assistant-transport/types.d.ts.map +1 -1
  17. package/dist/legacy-runtime/runtime-cores/assistant-transport/useAssistantTransportRuntime.d.ts.map +1 -1
  18. package/dist/legacy-runtime/runtime-cores/assistant-transport/useAssistantTransportRuntime.js +44 -3
  19. package/dist/legacy-runtime/runtime-cores/assistant-transport/useAssistantTransportRuntime.js.map +1 -1
  20. package/dist/primitives/actionBar/ActionBarExportMarkdown.js +1 -1
  21. package/dist/primitives/actionBar/ActionBarExportMarkdown.js.map +1 -1
  22. package/dist/primitives/attachment/AttachmentThumb.d.ts.map +1 -1
  23. package/dist/primitives/attachment/AttachmentThumb.js +17 -14
  24. package/dist/primitives/attachment/AttachmentThumb.js.map +1 -1
  25. package/dist/primitives/composer/ComposerInput.js +1 -0
  26. package/dist/primitives/composer/ComposerInput.js.map +1 -1
  27. package/dist/primitives/queueItem/QueueItemText.d.ts.map +1 -1
  28. package/dist/primitives/queueItem/QueueItemText.js +10 -4
  29. package/dist/primitives/queueItem/QueueItemText.js.map +1 -1
  30. package/dist/primitives/selectionToolbar/SelectionToolbarRoot.d.ts.map +1 -1
  31. package/dist/primitives/selectionToolbar/SelectionToolbarRoot.js +5 -4
  32. package/dist/primitives/selectionToolbar/SelectionToolbarRoot.js.map +1 -1
  33. package/dist/utils/createActionButton.js +1 -1
  34. package/dist/utils/createActionButton.js.map +1 -1
  35. package/dist/utils/useToolArgsFieldStatus.d.ts +2 -2
  36. package/package.json +6 -6
  37. package/src/client/ExternalThread.ts +28 -16
  38. package/src/context/providers/MessageProvider.tsx +8 -6
  39. package/src/index.ts +5 -1
  40. package/src/legacy-runtime/AssistantRuntimeProvider.tsx +13 -3
  41. package/src/legacy-runtime/runtime-cores/assistant-transport/runManager.ts +14 -1
  42. package/src/legacy-runtime/runtime-cores/assistant-transport/transport-scheduling.test.ts +38 -0
  43. package/src/legacy-runtime/runtime-cores/assistant-transport/types.ts +5 -1
  44. package/src/legacy-runtime/runtime-cores/assistant-transport/useAssistantTransport.spec.md +7 -0
  45. package/src/legacy-runtime/runtime-cores/assistant-transport/useAssistantTransportRuntime.test.tsx +290 -0
  46. package/src/legacy-runtime/runtime-cores/assistant-transport/useAssistantTransportRuntime.ts +62 -1
  47. package/src/primitives/actionBar/ActionBarExportMarkdown.tsx +1 -1
  48. package/src/primitives/attachment/AttachmentThumb.test.tsx +70 -0
  49. package/src/primitives/attachment/AttachmentThumb.tsx +8 -4
  50. package/src/primitives/composer/ComposerInput.test.tsx +53 -2
  51. package/src/primitives/composer/ComposerInput.tsx +3 -0
  52. package/src/primitives/queueItem/QueueItemText.tsx +8 -2
  53. package/src/primitives/selectionToolbar/SelectionToolbarRoot.test.tsx +74 -0
  54. package/src/primitives/selectionToolbar/SelectionToolbarRoot.tsx +3 -3
  55. package/src/tests/external-thread-parity.test.tsx +9 -4
  56. package/src/tests/local-runtime-queue.test.tsx +199 -6
  57. package/src/utils/createActionButton.test.tsx +18 -0
  58. package/src/utils/createActionButton.tsx +1 -1
@@ -4,6 +4,7 @@ import { getSelectionMessageId } from "../../utils/getSelectionMessageId.js";
4
4
  import { c } from "@assistant-ui/tap/react-shim/compiler-runtime";
5
5
  import { createContext, forwardRef, useContext, useEffect, useState } from "@assistant-ui/tap/react-shim";
6
6
  import { jsx } from "react/jsx-runtime";
7
+ import { composeEventHandlers } from "@radix-ui/primitive";
7
8
  import { createPortal } from "react-dom";
8
9
  //#region src/primitives/selectionToolbar/SelectionToolbarRoot.tsx
9
10
  const SelectionToolbarContext = createContext(null);
@@ -117,10 +118,7 @@ const SelectionToolbarPrimitiveRoot = forwardRef((t0, forwardedRef) => {
117
118
  const positionStyle = t5;
118
119
  let t6;
119
120
  if ($[10] !== onMouseDown) {
120
- t6 = (e) => {
121
- e.preventDefault();
122
- onMouseDown?.(e);
123
- };
121
+ t6 = composeEventHandlers(onMouseDown, _temp);
124
122
  $[10] = onMouseDown;
125
123
  $[11] = t6;
126
124
  } else t6 = $[11];
@@ -151,6 +149,9 @@ const SelectionToolbarPrimitiveRoot = forwardRef((t0, forwardedRef) => {
151
149
  return t8;
152
150
  });
153
151
  SelectionToolbarPrimitiveRoot.displayName = "SelectionToolbarPrimitive.Root";
152
+ function _temp(e) {
153
+ e.preventDefault();
154
+ }
154
155
  //#endregion
155
156
  export { SelectionToolbarPrimitiveRoot, useSelectionToolbarInfo };
156
157
 
@@ -1 +1 @@
1
- {"version":3,"file":"SelectionToolbarRoot.js","names":["c","_c","Primitive","ComponentPropsWithoutRef","ComponentRef","createContext","forwardRef","useContext","useEffect","useState","createPortal","getSelectionMessageId","SelectionInfo","text","messageId","rect","DOMRect","SelectionToolbarContext","useSelectionToolbarInfo","SelectionToolbarPrimitiveRoot","Element","div","Props","t0","forwardedRef","$","onMouseDown","props","style","info","setInfo","t1","t2","Symbol","for","checkSelection","requestAnimationFrame","sel","window","getSelection","isCollapsed","toString","trim","range","getRangeAt","getBoundingClientRect","handleSelectionCollapse","sel_0","handleScroll","document","addEventListener","removeEventListener","t3","top","t4","left","width","t5","position","transform","zIndex","positionStyle","t6","e","preventDefault","t7","t8","body","displayName"],"sources":["../../../src/primitives/selectionToolbar/SelectionToolbarRoot.tsx"],"sourcesContent":["\"use client\";\n\nimport { Primitive } from \"../../utils/Primitive\";\nimport {\n type ComponentPropsWithoutRef,\n type ComponentRef,\n createContext,\n forwardRef,\n useContext,\n useEffect,\n useState,\n} from \"react\";\nimport { createPortal } from \"react-dom\";\nimport { getSelectionMessageId } from \"../../utils/getSelectionMessageId\";\n\ntype SelectionInfo = {\n text: string;\n messageId: string;\n rect: DOMRect;\n};\n\nconst SelectionToolbarContext = createContext<SelectionInfo | null>(null);\n\nexport const useSelectionToolbarInfo = () =>\n useContext(SelectionToolbarContext);\n\nexport namespace SelectionToolbarPrimitiveRoot {\n export type Element = ComponentRef<typeof Primitive.div>;\n export type Props = ComponentPropsWithoutRef<typeof Primitive.div>;\n}\n\n/**\n * A floating toolbar that appears when text is selected within a message.\n *\n * Listens for mouse and keyboard selection events, validates that the\n * selection is within a single message, and renders a positioned portal\n * near the selection. Prevents mousedown from clearing the selection.\n *\n * @example\n * ```tsx\n * <SelectionToolbarPrimitive.Root>\n * <SelectionToolbarPrimitive.Quote>Quote</SelectionToolbarPrimitive.Quote>\n * </SelectionToolbarPrimitive.Root>\n * ```\n */\nexport const SelectionToolbarPrimitiveRoot = forwardRef<\n SelectionToolbarPrimitiveRoot.Element,\n SelectionToolbarPrimitiveRoot.Props\n>(({ onMouseDown, style, ...props }, forwardedRef) => {\n const [info, setInfo] = useState<SelectionInfo | null>(null);\n\n useEffect(() => {\n const checkSelection = () => {\n requestAnimationFrame(() => {\n const sel = window.getSelection();\n if (!sel || sel.isCollapsed) {\n setInfo(null);\n return;\n }\n\n const text = sel.toString().trim();\n if (!text) {\n setInfo(null);\n return;\n }\n\n const messageId = getSelectionMessageId(sel);\n if (!messageId) {\n setInfo(null);\n return;\n }\n\n const range = sel.getRangeAt(0);\n const rect = range.getBoundingClientRect();\n setInfo({ text, messageId, rect });\n });\n };\n\n const handleSelectionCollapse = () => {\n const sel = window.getSelection();\n if (!sel || sel.isCollapsed) {\n setInfo(null);\n }\n };\n\n const handleScroll = () => {\n setInfo(null);\n };\n\n document.addEventListener(\"mouseup\", checkSelection);\n document.addEventListener(\"keyup\", checkSelection);\n document.addEventListener(\"selectionchange\", handleSelectionCollapse);\n document.addEventListener(\"scroll\", handleScroll, true);\n\n return () => {\n document.removeEventListener(\"mouseup\", checkSelection);\n document.removeEventListener(\"keyup\", checkSelection);\n document.removeEventListener(\"selectionchange\", handleSelectionCollapse);\n document.removeEventListener(\"scroll\", handleScroll, true);\n };\n }, []);\n\n if (!info) return null;\n\n const positionStyle: React.CSSProperties = {\n position: \"fixed\",\n top: `${info.rect.top - 8}px`,\n left: `${info.rect.left + info.rect.width / 2}px`,\n transform: \"translate(-50%, -100%)\",\n zIndex: 50,\n ...style,\n };\n\n return createPortal(\n <SelectionToolbarContext.Provider value={info}>\n <Primitive.div\n {...props}\n ref={forwardedRef}\n style={positionStyle}\n onMouseDown={(e) => {\n // Prevent mousedown from clearing the text selection\n e.preventDefault();\n onMouseDown?.(e);\n }}\n />\n </SelectionToolbarContext.Provider>,\n document.body,\n );\n});\n\nSelectionToolbarPrimitiveRoot.displayName = \"SelectionToolbarPrimitive.Root\";\n"],"mappings":"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"}
1
+ {"version":3,"file":"SelectionToolbarRoot.js","names":["c","_c","Primitive","composeEventHandlers","ComponentPropsWithoutRef","ComponentRef","createContext","forwardRef","useContext","useEffect","useState","createPortal","getSelectionMessageId","SelectionInfo","text","messageId","rect","DOMRect","SelectionToolbarContext","useSelectionToolbarInfo","SelectionToolbarPrimitiveRoot","Element","div","Props","t0","forwardedRef","$","onMouseDown","props","style","info","setInfo","t1","t2","Symbol","for","checkSelection","requestAnimationFrame","sel","window","getSelection","isCollapsed","toString","trim","range","getRangeAt","getBoundingClientRect","handleSelectionCollapse","sel_0","handleScroll","document","addEventListener","removeEventListener","t3","top","t4","left","width","t5","position","transform","zIndex","positionStyle","t6","_temp","t7","t8","body","displayName","e","preventDefault"],"sources":["../../../src/primitives/selectionToolbar/SelectionToolbarRoot.tsx"],"sourcesContent":["\"use client\";\n\nimport { Primitive } from \"../../utils/Primitive\";\nimport { composeEventHandlers } from \"@radix-ui/primitive\";\nimport {\n type ComponentPropsWithoutRef,\n type ComponentRef,\n createContext,\n forwardRef,\n useContext,\n useEffect,\n useState,\n} from \"react\";\nimport { createPortal } from \"react-dom\";\nimport { getSelectionMessageId } from \"../../utils/getSelectionMessageId\";\n\ntype SelectionInfo = {\n text: string;\n messageId: string;\n rect: DOMRect;\n};\n\nconst SelectionToolbarContext = createContext<SelectionInfo | null>(null);\n\nexport const useSelectionToolbarInfo = () =>\n useContext(SelectionToolbarContext);\n\nexport namespace SelectionToolbarPrimitiveRoot {\n export type Element = ComponentRef<typeof Primitive.div>;\n export type Props = ComponentPropsWithoutRef<typeof Primitive.div>;\n}\n\n/**\n * A floating toolbar that appears when text is selected within a message.\n *\n * Listens for mouse and keyboard selection events, validates that the\n * selection is within a single message, and renders a positioned portal\n * near the selection. Prevents mousedown from clearing the selection.\n *\n * @example\n * ```tsx\n * <SelectionToolbarPrimitive.Root>\n * <SelectionToolbarPrimitive.Quote>Quote</SelectionToolbarPrimitive.Quote>\n * </SelectionToolbarPrimitive.Root>\n * ```\n */\nexport const SelectionToolbarPrimitiveRoot = forwardRef<\n SelectionToolbarPrimitiveRoot.Element,\n SelectionToolbarPrimitiveRoot.Props\n>(({ onMouseDown, style, ...props }, forwardedRef) => {\n const [info, setInfo] = useState<SelectionInfo | null>(null);\n\n useEffect(() => {\n const checkSelection = () => {\n requestAnimationFrame(() => {\n const sel = window.getSelection();\n if (!sel || sel.isCollapsed) {\n setInfo(null);\n return;\n }\n\n const text = sel.toString().trim();\n if (!text) {\n setInfo(null);\n return;\n }\n\n const messageId = getSelectionMessageId(sel);\n if (!messageId) {\n setInfo(null);\n return;\n }\n\n const range = sel.getRangeAt(0);\n const rect = range.getBoundingClientRect();\n setInfo({ text, messageId, rect });\n });\n };\n\n const handleSelectionCollapse = () => {\n const sel = window.getSelection();\n if (!sel || sel.isCollapsed) {\n setInfo(null);\n }\n };\n\n const handleScroll = () => {\n setInfo(null);\n };\n\n document.addEventListener(\"mouseup\", checkSelection);\n document.addEventListener(\"keyup\", checkSelection);\n document.addEventListener(\"selectionchange\", handleSelectionCollapse);\n document.addEventListener(\"scroll\", handleScroll, true);\n\n return () => {\n document.removeEventListener(\"mouseup\", checkSelection);\n document.removeEventListener(\"keyup\", checkSelection);\n document.removeEventListener(\"selectionchange\", handleSelectionCollapse);\n document.removeEventListener(\"scroll\", handleScroll, true);\n };\n }, []);\n\n if (!info) return null;\n\n const positionStyle: React.CSSProperties = {\n position: \"fixed\",\n top: `${info.rect.top - 8}px`,\n left: `${info.rect.left + info.rect.width / 2}px`,\n transform: \"translate(-50%, -100%)\",\n zIndex: 50,\n ...style,\n };\n\n return createPortal(\n <SelectionToolbarContext.Provider value={info}>\n <Primitive.div\n {...props}\n ref={forwardedRef}\n style={positionStyle}\n onMouseDown={composeEventHandlers(onMouseDown, (e) => {\n // Prevent mousedown from clearing the text selection\n e.preventDefault();\n })}\n />\n </SelectionToolbarContext.Provider>,\n document.body,\n );\n});\n\nSelectionToolbarPrimitiveRoot.displayName = \"SelectionToolbarPrimitive.Root\";\n"],"mappings":"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"}
@@ -21,8 +21,8 @@ const createActionButton = (displayName, useActionButton, forwardProps = []) =>
21
21
  let t4;
22
22
  if ($[0] !== forwardedRef || $[1] !== primitiveProps || $[2] !== t0.button || $[3] !== t2 || $[4] !== t3) {
23
23
  t4 = /* @__PURE__ */ jsx(t0.button, {
24
- ...primitiveProps,
25
24
  type: t1,
25
+ ...primitiveProps,
26
26
  ref: forwardedRef,
27
27
  disabled: t2,
28
28
  onClick: t3
@@ -1 +1 @@
1
- {"version":3,"file":"createActionButton.js","names":["ComponentRef","forwardRef","ComponentPropsWithoutRef","MouseEventHandler","Primitive","composeEventHandlers","ActionButtonCallback","props","TProps","HTMLButtonElement","PrimitiveButtonProps","button","ActionButtonProps","THook","ActionButtonElement","createActionButton","displayName","useActionButton","forwardProps","NonNullable","ActionButton","forwardedRef","$","_c","forwardedProps","primitiveProps","Object","keys","Array","forEach","key","includes","callback","undefined","t0","t1","t2","disabled","t3","onClick","t4"],"sources":["../../src/utils/createActionButton.tsx"],"sourcesContent":["import {\n type ComponentRef,\n forwardRef,\n type ComponentPropsWithoutRef,\n type MouseEventHandler,\n} from \"react\";\nimport { Primitive } from \"./Primitive\";\nimport { composeEventHandlers } from \"@radix-ui/primitive\";\n\ntype ActionButtonCallback<TProps> = (\n props: TProps,\n) => MouseEventHandler<HTMLButtonElement> | null;\n\ntype PrimitiveButtonProps = ComponentPropsWithoutRef<typeof Primitive.button>;\n\nexport type ActionButtonProps<THook> = PrimitiveButtonProps &\n (THook extends (props: infer TProps) => unknown ? TProps : never);\n\nexport type ActionButtonElement = ComponentRef<typeof Primitive.button>;\n\nexport const createActionButton = <TProps,>(\n displayName: string,\n useActionButton: ActionButtonCallback<TProps>,\n forwardProps: (keyof NonNullable<TProps>)[] = [],\n) => {\n const ActionButton = forwardRef<\n ActionButtonElement,\n PrimitiveButtonProps & TProps\n >((props, forwardedRef) => {\n const forwardedProps = {} as TProps;\n const primitiveProps = {} as PrimitiveButtonProps;\n\n (Object.keys(props) as Array<keyof typeof props>).forEach((key) => {\n if (forwardProps.includes(key as keyof TProps)) {\n (forwardedProps as any)[key] = props[key];\n } else {\n (primitiveProps as any)[key] = props[key];\n }\n });\n\n const callback = useActionButton(forwardedProps as TProps) ?? undefined;\n return (\n <Primitive.button\n {...primitiveProps}\n type=\"button\"\n ref={forwardedRef}\n disabled={primitiveProps.disabled || !callback}\n onClick={composeEventHandlers(primitiveProps.onClick, callback)}\n />\n );\n });\n\n ActionButton.displayName = displayName;\n\n return ActionButton;\n};\n"],"mappings":";;;;;;AAoBA,MAAae,sBACXC,aACAC,iBACAC,eAA8C,CAAA,MAC3C;CACH,MAAME,eAAenB,YAGnBM,OAAAc,iBAAA;EAAA,MAAAC,IAAAC,EAAA,CAAA;EACA,MAAAC,iBAAuB,CAAC;EACxB,MAAAC,iBAAuB,CAAC;EAExB,OAAOE,KAAMpB,KAAK,CAAC,CAA6BsB,SAAUC,QAAA;GACxD,IAAIZ,aAAYa,SAAUD,GAAmB,GAC3C,eAAwBA,OAAOvB,MAAMuB;QAErC,eAAwBA,OAAOvB,MAAMuB;EACtC,CACF;EAED,MAAAE,WAAiBf,gBAAgBO,cAAqC,KAArDS,KAAAA;EAEd,MAAAC,KAAA9B;EAEM,MAAA+B,KAAA;EAEK,MAAAC,KAAAX,eAAcY,YAAd,CAA4BL;EAC7B,MAAAM,KAAAjC,qBAAqBoB,eAAcc,SAAUP,QAAQ;EAAC,IAAAQ;EAAA,IAAAlB,EAAA,OAAAD,gBAAAC,EAAA,OAAAG,kBAAAH,EAAA,OAAAY,GAAAvB,UAAAW,EAAA,OAAAc,MAAAd,EAAA,OAAAgB,IAAA;GALjEE,KAAA,oBAAA,GAAA,QAAA;IAAA,GACMf;IACC,MAAAU;IACAd,KAAAA;IACK,UAAAe;IACD,SAAAE;GAAsD,CAAA;GAC/DhB,EAAA,KAAAD;GAAAC,EAAA,KAAAG;GAAAH,EAAA,KAAAY,GAAAvB;GAAAW,EAAA,KAAAc;GAAAd,EAAA,KAAAgB;GAAAhB,EAAA,KAAAkB;EAAA,OAAAA,KAAAlB,EAAA;EAAA,OANFkB;CAME,CAEL;CAEDpB,aAAaJ,cAAcA;CAE3B,OAAOI;AACT"}
1
+ {"version":3,"file":"createActionButton.js","names":["ComponentRef","forwardRef","ComponentPropsWithoutRef","MouseEventHandler","Primitive","composeEventHandlers","ActionButtonCallback","props","TProps","HTMLButtonElement","PrimitiveButtonProps","button","ActionButtonProps","THook","ActionButtonElement","createActionButton","displayName","useActionButton","forwardProps","NonNullable","ActionButton","forwardedRef","$","_c","forwardedProps","primitiveProps","Object","keys","Array","forEach","key","includes","callback","undefined","t0","t1","t2","disabled","t3","onClick","t4"],"sources":["../../src/utils/createActionButton.tsx"],"sourcesContent":["import {\n type ComponentRef,\n forwardRef,\n type ComponentPropsWithoutRef,\n type MouseEventHandler,\n} from \"react\";\nimport { Primitive } from \"./Primitive\";\nimport { composeEventHandlers } from \"@radix-ui/primitive\";\n\ntype ActionButtonCallback<TProps> = (\n props: TProps,\n) => MouseEventHandler<HTMLButtonElement> | null;\n\ntype PrimitiveButtonProps = ComponentPropsWithoutRef<typeof Primitive.button>;\n\nexport type ActionButtonProps<THook> = PrimitiveButtonProps &\n (THook extends (props: infer TProps) => unknown ? TProps : never);\n\nexport type ActionButtonElement = ComponentRef<typeof Primitive.button>;\n\nexport const createActionButton = <TProps,>(\n displayName: string,\n useActionButton: ActionButtonCallback<TProps>,\n forwardProps: (keyof NonNullable<TProps>)[] = [],\n) => {\n const ActionButton = forwardRef<\n ActionButtonElement,\n PrimitiveButtonProps & TProps\n >((props, forwardedRef) => {\n const forwardedProps = {} as TProps;\n const primitiveProps = {} as PrimitiveButtonProps;\n\n (Object.keys(props) as Array<keyof typeof props>).forEach((key) => {\n if (forwardProps.includes(key as keyof TProps)) {\n (forwardedProps as any)[key] = props[key];\n } else {\n (primitiveProps as any)[key] = props[key];\n }\n });\n\n const callback = useActionButton(forwardedProps as TProps) ?? undefined;\n return (\n <Primitive.button\n type=\"button\"\n {...primitiveProps}\n ref={forwardedRef}\n disabled={primitiveProps.disabled || !callback}\n onClick={composeEventHandlers(primitiveProps.onClick, callback)}\n />\n );\n });\n\n ActionButton.displayName = displayName;\n\n return ActionButton;\n};\n"],"mappings":";;;;;;AAoBA,MAAae,sBACXC,aACAC,iBACAC,eAA8C,CAAA,MAC3C;CACH,MAAME,eAAenB,YAGnBM,OAAAc,iBAAA;EAAA,MAAAC,IAAAC,EAAA,CAAA;EACA,MAAAC,iBAAuB,CAAC;EACxB,MAAAC,iBAAuB,CAAC;EAExB,OAAOE,KAAMpB,KAAK,CAAC,CAA6BsB,SAAUC,QAAA;GACxD,IAAIZ,aAAYa,SAAUD,GAAmB,GAC3C,eAAwBA,OAAOvB,MAAMuB;QAErC,eAAwBA,OAAOvB,MAAMuB;EACtC,CACF;EAED,MAAAE,WAAiBf,gBAAgBO,cAAqC,KAArDS,KAAAA;EAEd,MAAAC,KAAA9B;EACM,MAAA+B,KAAA;EAGK,MAAAC,KAAAX,eAAcY,YAAd,CAA4BL;EAC7B,MAAAM,KAAAjC,qBAAqBoB,eAAcc,SAAUP,QAAQ;EAAC,IAAAQ;EAAA,IAAAlB,EAAA,OAAAD,gBAAAC,EAAA,OAAAG,kBAAAH,EAAA,OAAAY,GAAAvB,UAAAW,EAAA,OAAAc,MAAAd,EAAA,OAAAgB,IAAA;GALjEE,KAAA,oBAAA,GAAA,QAAA;IACO,MAAAL;IAAQ,GACTV;IACCJ,KAAAA;IACK,UAAAe;IACD,SAAAE;GAAsD,CAAA;GAC/DhB,EAAA,KAAAD;GAAAC,EAAA,KAAAG;GAAAH,EAAA,KAAAY,GAAAvB;GAAAW,EAAA,KAAAc;GAAAd,EAAA,KAAAgB;GAAAhB,EAAA,KAAAkB;EAAA,OAAAA,KAAAlB,EAAA;EAAA,OANFkB;CAME,CAEL;CAEDpB,aAAaJ,cAAcA;CAE3B,OAAOI;AACT"}
@@ -1,5 +1,7 @@
1
1
  //#region src/utils/useToolArgsFieldStatus.d.ts
2
2
  declare const useToolArgsFieldStatus: (fieldPath: (string | number)[]) => {
3
+ type: string;
4
+ } | {
3
5
  readonly type: "running";
4
6
  } | {
5
7
  readonly type: "complete";
@@ -7,8 +9,6 @@ declare const useToolArgsFieldStatus: (fieldPath: (string | number)[]) => {
7
9
  readonly type: "incomplete";
8
10
  readonly reason: "cancelled" | "length" | "content-filter" | "other" | "error";
9
11
  readonly error?: unknown;
10
- } | {
11
- type: string;
12
12
  };
13
13
  //#endregion
14
14
  export { useToolArgsFieldStatus };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@assistant-ui/react",
3
- "version": "0.15.4",
3
+ "version": "0.15.5",
4
4
  "description": "Open-source TypeScript/React library for building production-grade AI chat experiences",
5
5
  "keywords": [
6
6
  "ai",
@@ -55,9 +55,9 @@
55
55
  ],
56
56
  "sideEffects": false,
57
57
  "dependencies": {
58
- "@assistant-ui/core": "^0.3.5",
59
- "@assistant-ui/store": "^0.3.3",
60
- "@assistant-ui/tap": "^0.9.9",
58
+ "@assistant-ui/core": "^0.3.6",
59
+ "@assistant-ui/store": "^0.3.4",
60
+ "@assistant-ui/tap": "^0.9.10",
61
61
  "@radix-ui/primitive": "^1.1.7",
62
62
  "@radix-ui/react-collection": "^1.1.15",
63
63
  "@radix-ui/react-compose-refs": "^1.1.5",
@@ -67,7 +67,7 @@
67
67
  "@radix-ui/react-use-controllable-state": "^1.2.6",
68
68
  "@radix-ui/react-use-escape-keydown": "^1.1.5",
69
69
  "assistant-cloud": "^0.1.38",
70
- "assistant-stream": "^0.3.33",
70
+ "assistant-stream": "^0.3.34",
71
71
  "nanoid": "^6.0.0",
72
72
  "radix-ui": "^1.6.7",
73
73
  "react-textarea-autosize": "^8.5.9",
@@ -100,7 +100,7 @@
100
100
  "react-dom": "^19.2.8",
101
101
  "vitest": "^4.1.10",
102
102
  "@assistant-ui/vite": "0.0.11",
103
- "@assistant-ui/x-buildutils": "0.0.20"
103
+ "@assistant-ui/x-buildutils": "0.0.21"
104
104
  },
105
105
  "publishConfig": {
106
106
  "access": "public",
@@ -32,6 +32,7 @@ import type {
32
32
  ToolCallMessagePartStatus,
33
33
  ExternalThreadQueueAdapter,
34
34
  ExternalThreadBranchAdapter,
35
+ QueuePlacement,
35
36
  } from "@assistant-ui/core";
36
37
  import { ToolResponse } from "assistant-stream";
37
38
  import type { ReadonlyJSONValue } from "assistant-stream/utils";
@@ -175,7 +176,6 @@ const useMessageClient = ({
175
176
  };
176
177
 
177
178
  const handleSendEdit = (msg: AppendMessage) => {
178
- queue?.clear("edit");
179
179
  onEdit?.({
180
180
  ...msg,
181
181
  parentId,
@@ -395,16 +395,17 @@ type ComposerClientResourceProps = {
395
395
 
396
396
  const useQueueItemClient = ({
397
397
  item,
398
- onSteer,
398
+ onMove,
399
399
  onRemove,
400
400
  }: {
401
401
  item: QueueItemState;
402
- onSteer: () => void;
402
+ onMove: (placement: QueuePlacement) => void;
403
403
  onRemove: () => void;
404
404
  }): ClientOutput<"queueItem"> => {
405
405
  return {
406
406
  getState: () => item,
407
- steer: onSteer,
407
+ steer: () => onMove({ lane: "steer", insertAfter: null }),
408
+ move: onMove,
408
409
  remove: onRemove,
409
410
  };
410
411
  };
@@ -522,14 +523,24 @@ const useComposerClientResource = ({
522
523
  });
523
524
  };
524
525
 
525
- const queueItems = queue?.items ?? EMPTY_QUEUE_ITEMS;
526
+ const steerItems = queue?.steerItems ?? EMPTY_QUEUE_ITEMS;
527
+ const laneItems = queue?.items ?? EMPTY_QUEUE_ITEMS;
528
+ const queueItems = useMemo(
529
+ () =>
530
+ steerItems.length === 0
531
+ ? laneItems
532
+ : laneItems.length === 0
533
+ ? steerItems
534
+ : [...steerItems, ...laneItems],
535
+ [steerItems, laneItems],
536
+ );
526
537
  const queueItemClients = useClientLookup(
527
538
  queueItems.map((item) =>
528
539
  withKey(
529
540
  item.id,
530
541
  QueueItemClient({
531
542
  item,
532
- onSteer: () => queue?.steer(item.id),
543
+ onMove: (placement) => queue?.move(item.id, placement),
533
544
  onRemove: () => queue?.remove(item.id),
534
545
  }),
535
546
  ),
@@ -662,7 +673,8 @@ const useComposerClientResource = ({
662
673
  },
663
674
  };
664
675
  if (queue) {
665
- queue.enqueue(composedMessage, { steer: opts?.steer ?? false });
676
+ if (opts?.steer ?? canCancel) queue.steer(composedMessage);
677
+ else queue.enqueue(composedMessage);
666
678
  } else {
667
679
  onSend?.(composedMessage);
668
680
  }
@@ -697,7 +709,10 @@ const useComposerClientResource = ({
697
709
  startDictation: () => {},
698
710
  stopDictation: () => {},
699
711
  setQuote,
700
- queueItem: (selector: { index: number }) => {
712
+ queueItem: (selector: { index: number } | { id: string }) => {
713
+ if ("id" in selector) {
714
+ return queueItemClients.get({ key: selector.id });
715
+ }
701
716
  return queueItemClients.get(selector);
702
717
  },
703
718
  };
@@ -754,7 +769,6 @@ const useExternalThread = ({
754
769
  if (messageIndex === -1) return;
755
770
 
756
771
  const parentId = messageIndex > 0 ? messages[messageIndex - 1]!.id : null;
757
- queue?.clear("reload");
758
772
  onReload?.(parentId);
759
773
  };
760
774
 
@@ -778,7 +792,6 @@ const useExternalThread = ({
778
792
  );
779
793
 
780
794
  const handleCancelRun = () => {
781
- queue?.clear("cancel-run");
782
795
  onCancel?.();
783
796
  };
784
797
 
@@ -793,11 +806,10 @@ const useExternalThread = ({
793
806
  (): ExternalThreadQueueAdapter | undefined =>
794
807
  queue && {
795
808
  ...queue,
796
- enqueue: (message, options) =>
797
- queue.enqueue(
798
- { ...message, parentId: message.parentId ?? headId },
799
- options,
800
- ),
809
+ enqueue: (message) =>
810
+ queue.enqueue({ ...message, parentId: message.parentId ?? headId }),
811
+ steer: (message) =>
812
+ queue.steer({ ...message, parentId: message.parentId ?? headId }),
801
813
  },
802
814
  [queue, headId],
803
815
  );
@@ -898,7 +910,7 @@ const useExternalThread = ({
898
910
  startRun: message.startRun,
899
911
  };
900
912
  if (queue) {
901
- queue.enqueue(appendMessage, { steer: false });
913
+ queue.enqueue(appendMessage);
902
914
  } else {
903
915
  onNew?.(appendMessage);
904
916
  }
@@ -1,7 +1,7 @@
1
1
  "use client";
2
2
 
3
3
  import type { FC, PropsWithChildren } from "react";
4
- import { useAui, AuiProvider } from "@assistant-ui/store";
4
+ import { useAui, AuiConfig, AuiProvider } from "@assistant-ui/store";
5
5
  import {
6
6
  type ThreadMessageClientProps,
7
7
  ThreadMessageClient,
@@ -10,9 +10,11 @@ import {
10
10
  export const MessageProvider: FC<
11
11
  PropsWithChildren<ThreadMessageClientProps>
12
12
  > = ({ children, ...props }) => {
13
- const aui = useAui({
14
- message: ThreadMessageClient(props),
15
- });
16
-
17
- return <AuiProvider value={aui}>{children}</AuiProvider>;
13
+ const aui = useAui();
14
+ const config = AuiConfig({ message: ThreadMessageClient(props) });
15
+ return (
16
+ <AuiProvider extends={aui} config={config}>
17
+ {children}
18
+ </AuiProvider>
19
+ );
18
20
  };
package/src/index.ts CHANGED
@@ -4,6 +4,7 @@
4
4
  export {
5
5
  useAui,
6
6
  AuiProvider,
7
+ AuiConfig,
7
8
  useAuiState,
8
9
  useAuiEvent,
9
10
  AuiIf,
@@ -167,7 +168,10 @@ export type { LocalRuntimeOptions } from "./legacy-runtime/runtime-cores/local/L
167
168
  // --- remote-thread-list ---
168
169
  export { useRemoteThreadListRuntime } from "./legacy-runtime/runtime-cores/remote-thread-list/useRemoteThreadListRuntime";
169
170
  export { useCloudThreadListAdapter } from "./legacy-runtime/runtime-cores/remote-thread-list/adapter/cloud";
170
- export type { RemoteThreadListAdapter } from "@assistant-ui/core";
171
+ export type {
172
+ RemoteThreadListAdapter,
173
+ RemoteThreadListProviderComponent,
174
+ } from "@assistant-ui/core";
171
175
  export { InMemoryThreadListAdapter } from "@assistant-ui/core";
172
176
 
173
177
  // Re-export from @assistant-ui/core (runtime-cores root)
@@ -1,7 +1,11 @@
1
1
  "use client";
2
2
 
3
3
  import { type FC, memo, type PropsWithChildren, useEffect } from "react";
4
- import { type AssistantClient, useAui } from "@assistant-ui/store";
4
+ import {
5
+ type AssistantClient,
6
+ type AuiConfig,
7
+ useAui,
8
+ } from "@assistant-ui/store";
5
9
  import type { AssistantRuntime } from "./runtime/AssistantRuntime";
6
10
  import { AssistantProviderBase } from "@assistant-ui/core/react";
7
11
  import { ThreadPrimitiveViewportProvider } from "../context/providers/ThreadViewportProvider";
@@ -24,6 +28,12 @@ export namespace AssistantRuntimeProvider {
24
28
  * @defaultValue undefined
25
29
  */
26
30
  aui?: AssistantClient;
31
+
32
+ /**
33
+ * Optional extra scopes provided alongside the runtime's `threads`
34
+ * scope; build with `AuiConfig`.
35
+ */
36
+ config?: AuiConfig;
27
37
  }>;
28
38
  }
29
39
 
@@ -39,9 +49,9 @@ const DevToolsRegistration: FC = () => {
39
49
 
40
50
  export const AssistantRuntimeProviderImpl: FC<
41
51
  AssistantRuntimeProvider.Props
42
- > = ({ children, aui, runtime }) => {
52
+ > = ({ children, aui, config, runtime }) => {
43
53
  return (
44
- <AssistantProviderBase runtime={runtime} aui={aui ?? null}>
54
+ <AssistantProviderBase runtime={runtime} aui={aui ?? null} config={config}>
45
55
  <DevToolsRegistration />
46
56
  {/* TODO temporarily allow accessing viewport state from outside the viewport */}
47
57
  {/* TODO figure out if this behavior should be deprecated, since it is quite hacky */}
@@ -9,6 +9,13 @@ export type RunManager = Readonly<{
9
9
 
10
10
  const disposeReason = Symbol("assistant-transport-dispose");
11
11
 
12
+ const reportFinishError = (error: unknown) => {
13
+ console.error(
14
+ "[assistant-ui] Assistant transport onFinish callback threw an error",
15
+ error,
16
+ );
17
+ };
18
+
12
19
  export function useRunManager(config: {
13
20
  onRun: (signal: AbortSignal) => Promise<void>;
14
21
  onFinish?: (() => void) | undefined;
@@ -52,7 +59,13 @@ export function useRunManager(config: {
52
59
  }
53
60
  } finally {
54
61
  if (!disposeAborted() && !stateRef.current.disposed) {
55
- onFinishRef.current?.();
62
+ try {
63
+ void Promise.resolve(onFinishRef.current?.()).catch(
64
+ reportFinishError,
65
+ );
66
+ } catch (error) {
67
+ reportFinishError(error);
68
+ }
56
69
  }
57
70
  if (!stateRef.current.disposed && stateRef.current.pending) {
58
71
  startRun();
@@ -146,6 +146,44 @@ describe("assistant transport scheduling contracts", () => {
146
146
  expect(seen[0]).toEqual([createMessageCommand("m1")]);
147
147
  });
148
148
 
149
+ it("settles the run when onFinish throws", async () => {
150
+ const error = new Error("telemetry failed");
151
+ const onFinish = vi.fn<() => void>().mockImplementationOnce(() => {
152
+ throw error;
153
+ });
154
+ const consoleError = vi
155
+ .spyOn(console, "error")
156
+ .mockImplementation(() => {});
157
+
158
+ try {
159
+ const { result } = renderHook(() =>
160
+ useTransportSchedulingHarness({ onFinish }),
161
+ );
162
+
163
+ act(() => {
164
+ result.current.commandQueue.enqueue(createMessageCommand("m1"));
165
+ });
166
+
167
+ await waitFor(() => {
168
+ expect(result.current.runManager.isRunning).toBe(false);
169
+ });
170
+
171
+ act(() => {
172
+ result.current.commandQueue.enqueue(createMessageCommand("m2"));
173
+ });
174
+
175
+ await waitFor(() => {
176
+ expect(result.current.runBatchesRef.current).toHaveLength(2);
177
+ });
178
+ expect(consoleError).toHaveBeenCalledWith(
179
+ "[assistant-ui] Assistant transport onFinish callback threw an error",
180
+ error,
181
+ );
182
+ } finally {
183
+ consoleError.mockRestore();
184
+ }
185
+ });
186
+
149
187
  it("cancel returns combined in-flight and queued commands", async () => {
150
188
  const onCancel = vi.fn();
151
189
  const { result } = renderHook(() =>
@@ -92,7 +92,9 @@ export type AssistantTransportProtocol = "data-stream" | "assistant-transport";
92
92
 
93
93
  export type SendCommandsRequestBody = {
94
94
  commands: QueuedCommand[];
95
- state: unknown;
95
+ /** Absent on a resume with `resumeStateApi`; the server replays from its retained snapshot. */
96
+ state?: unknown;
97
+ runId?: string;
96
98
  system: string | undefined;
97
99
  tools: Record<string, unknown> | undefined;
98
100
  callSettings: LanguageModelV1CallSettings | undefined;
@@ -109,6 +111,8 @@ export type AssistantTransportOptions<T> = {
109
111
  initialState: T;
110
112
  api: string;
111
113
  resumeApi?: string;
114
+ /** Endpoint that returns the retained initial state and run ID for a resume stream. A 204 response means no run is active and the resume is skipped. */
115
+ resumeStateApi?: string;
112
116
  protocol?: AssistantTransportProtocol;
113
117
  /**
114
118
  * When `false`, stream decoding and state reconciliation tolerate malformed
@@ -16,6 +16,13 @@ Command Scheduling
16
16
  - If no run is in progress: start a run immediately and flush commands to the server.
17
17
  - A follow-up run that finds an empty queue is a no-op: no request is sent and no error is surfaced.
18
18
  - A resume run sends no commands; commands enqueued while it is pending or active are flushed in a follow-up run after it settles.
19
+
20
+ Resume State
21
+
22
+ - With `resumeStateApi` configured, a resume first posts `{ threadId }` there; the endpoint returns `{ runId, state }`, the state that started the active run.
23
+ - The resume request carries `runId` and no `state`: the server replays from the snapshot it retained for that run, so neither `body` overrides nor a `prepareSendCommandsRequest` rebuild can substitute a different base. `runId` takes precedence over `body` fields and is re-attached after `prepareSendCommandsRequest` so a rebuilt body cannot drop it. The local base is replaced by the snapshot only after the resume stream responds OK, so a rejected resume keeps the local state.
24
+ - A 204 response means no active run: the resume is skipped without error, and queued commands are flushed in a follow-up run.
25
+ - A malformed snapshot response fails the resume before the replay request is sent.
19
26
  - Runs execute on a `queueMicrotask`, so multiple synchronous enqueues coalesce into a single request: the first run's flush takes all of them, and the coalesced follow-up run no-ops.
20
27
 
21
28
  Command Queue