@assistant-ui/react 0.12.14 → 0.12.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 (39) hide show
  1. package/dist/legacy-runtime/runtime-cores/assistant-transport/useToolInvocations.d.ts.map +1 -1
  2. package/dist/legacy-runtime/runtime-cores/assistant-transport/useToolInvocations.js +143 -38
  3. package/dist/legacy-runtime/runtime-cores/assistant-transport/useToolInvocations.js.map +1 -1
  4. package/dist/legacy-runtime/runtime-cores/external-store/external-message-converter.d.ts.map +1 -1
  5. package/dist/legacy-runtime/runtime-cores/external-store/external-message-converter.js +21 -9
  6. package/dist/legacy-runtime/runtime-cores/external-store/external-message-converter.js.map +1 -1
  7. package/dist/primitives/actionBar/ActionBarInteractionContext.d.ts +6 -0
  8. package/dist/primitives/actionBar/ActionBarInteractionContext.d.ts.map +1 -0
  9. package/dist/primitives/actionBar/ActionBarInteractionContext.js +5 -0
  10. package/dist/primitives/actionBar/ActionBarInteractionContext.js.map +1 -0
  11. package/dist/primitives/actionBar/ActionBarRoot.d.ts.map +1 -1
  12. package/dist/primitives/actionBar/ActionBarRoot.js +18 -4
  13. package/dist/primitives/actionBar/ActionBarRoot.js.map +1 -1
  14. package/dist/primitives/actionBar/useActionBarFloatStatus.d.ts +2 -1
  15. package/dist/primitives/actionBar/useActionBarFloatStatus.d.ts.map +1 -1
  16. package/dist/primitives/actionBar/useActionBarFloatStatus.js +3 -2
  17. package/dist/primitives/actionBar/useActionBarFloatStatus.js.map +1 -1
  18. package/dist/primitives/actionBarMore/ActionBarMoreRoot.d.ts.map +1 -1
  19. package/dist/primitives/actionBarMore/ActionBarMoreRoot.js +35 -2
  20. package/dist/primitives/actionBarMore/ActionBarMoreRoot.js.map +1 -1
  21. package/dist/utils/json/is-json-equal.d.ts +2 -0
  22. package/dist/utils/json/is-json-equal.d.ts.map +1 -0
  23. package/dist/utils/json/is-json-equal.js +31 -0
  24. package/dist/utils/json/is-json-equal.js.map +1 -0
  25. package/dist/utils/json/is-json.d.ts +1 -0
  26. package/dist/utils/json/is-json.d.ts.map +1 -1
  27. package/dist/utils/json/is-json.js +5 -3
  28. package/dist/utils/json/is-json.js.map +1 -1
  29. package/package.json +6 -6
  30. package/src/legacy-runtime/runtime-cores/assistant-transport/useToolInvocations.test.ts +225 -2
  31. package/src/legacy-runtime/runtime-cores/assistant-transport/useToolInvocations.ts +191 -50
  32. package/src/legacy-runtime/runtime-cores/external-store/external-message-converter.ts +28 -10
  33. package/src/primitives/actionBar/ActionBarInteractionContext.ts +13 -0
  34. package/src/primitives/actionBar/ActionBarRoot.tsx +38 -8
  35. package/src/primitives/actionBar/useActionBarFloatStatus.ts +4 -1
  36. package/src/primitives/actionBarMore/ActionBarMoreRoot.tsx +52 -2
  37. package/src/tests/external-message-converter.test.ts +80 -0
  38. package/src/utils/json/is-json-equal.ts +48 -0
  39. package/src/utils/json/is-json.ts +6 -3
@@ -1,10 +1,43 @@
1
1
  "use client";
2
2
  import { jsx as _jsx } from "react/jsx-runtime";
3
+ import { useCallback, useEffect, useRef } from "react";
3
4
  import { DropdownMenu as DropdownMenuPrimitive } from "radix-ui";
4
5
  import { useDropdownMenuScope } from "./scope.js";
5
- export const ActionBarMorePrimitiveRoot = ({ __scopeActionBarMore, ...rest }) => {
6
+ import { useActionBarInteractionContext } from "../actionBar/ActionBarInteractionContext.js";
7
+ export const ActionBarMorePrimitiveRoot = ({ __scopeActionBarMore, open, onOpenChange, ...rest }) => {
6
8
  const scope = useDropdownMenuScope(__scopeActionBarMore);
7
- return _jsx(DropdownMenuPrimitive.Root, { ...scope, ...rest });
9
+ const actionBarInteraction = useActionBarInteractionContext();
10
+ const releaseInteractionLockRef = useRef(null);
11
+ const isControlled = open !== undefined;
12
+ const setInteractionOpen = useCallback((nextOpen) => {
13
+ if (nextOpen) {
14
+ if (releaseInteractionLockRef.current)
15
+ return;
16
+ releaseInteractionLockRef.current =
17
+ actionBarInteraction?.acquireInteractionLock() ?? null;
18
+ return;
19
+ }
20
+ releaseInteractionLockRef.current?.();
21
+ releaseInteractionLockRef.current = null;
22
+ }, [actionBarInteraction]);
23
+ const handleOpenChange = useCallback((nextOpen) => {
24
+ if (!isControlled) {
25
+ setInteractionOpen(nextOpen);
26
+ }
27
+ onOpenChange?.(nextOpen);
28
+ }, [isControlled, setInteractionOpen, onOpenChange]);
29
+ useEffect(() => {
30
+ if (!isControlled)
31
+ return;
32
+ setInteractionOpen(Boolean(open));
33
+ }, [isControlled, open, setInteractionOpen]);
34
+ useEffect(() => {
35
+ return () => {
36
+ releaseInteractionLockRef.current?.();
37
+ releaseInteractionLockRef.current = null;
38
+ };
39
+ }, []);
40
+ return (_jsx(DropdownMenuPrimitive.Root, { ...scope, ...rest, ...(open !== undefined ? { open } : null), onOpenChange: handleOpenChange }));
8
41
  };
9
42
  ActionBarMorePrimitiveRoot.displayName = "ActionBarMorePrimitive.Root";
10
43
  //# sourceMappingURL=ActionBarMoreRoot.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"ActionBarMoreRoot.js","sourceRoot":"","sources":["../../../src/primitives/actionBarMore/ActionBarMoreRoot.tsx"],"names":[],"mappings":"AAAA,YAAY,CAAC;;AAGb,OAAO,EAAE,YAAY,IAAI,qBAAqB,EAAE,MAAM,UAAU,CAAC;AACjE,OAAO,EAAe,oBAAoB,EAAE,mBAAgB;AAM5D,MAAM,CAAC,MAAM,0BAA0B,GAEnC,CAAC,EACH,oBAAoB,EACpB,GAAG,IAAI,EACuC,EAAE,EAAE;IAClD,MAAM,KAAK,GAAG,oBAAoB,CAAC,oBAAoB,CAAC,CAAC;IAEzD,OAAO,KAAC,qBAAqB,CAAC,IAAI,OAAK,KAAK,KAAM,IAAI,GAAI,CAAC;AAC7D,CAAC,CAAC;AAEF,0BAA0B,CAAC,WAAW,GAAG,6BAA6B,CAAC"}
1
+ {"version":3,"file":"ActionBarMoreRoot.js","sourceRoot":"","sources":["../../../src/primitives/actionBarMore/ActionBarMoreRoot.tsx"],"names":[],"mappings":"AAAA,YAAY,CAAC;;AAEb,OAAO,EAAM,WAAW,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,OAAO,CAAC;AAC3D,OAAO,EAAE,YAAY,IAAI,qBAAqB,EAAE,MAAM,UAAU,CAAC;AACjE,OAAO,EAAe,oBAAoB,EAAE,mBAAgB;AAC5D,OAAO,EAAE,8BAA8B,EAAE,oDAAiD;AAM1F,MAAM,CAAC,MAAM,0BAA0B,GAEnC,CAAC,EACH,oBAAoB,EACpB,IAAI,EACJ,YAAY,EACZ,GAAG,IAAI,EACuC,EAAE,EAAE;IAClD,MAAM,KAAK,GAAG,oBAAoB,CAAC,oBAAoB,CAAC,CAAC;IACzD,MAAM,oBAAoB,GAAG,8BAA8B,EAAE,CAAC;IAC9D,MAAM,yBAAyB,GAAG,MAAM,CAAsB,IAAI,CAAC,CAAC;IACpE,MAAM,YAAY,GAAG,IAAI,KAAK,SAAS,CAAC;IAExC,MAAM,kBAAkB,GAAG,WAAW,CACpC,CAAC,QAAiB,EAAE,EAAE;QACpB,IAAI,QAAQ,EAAE,CAAC;YACb,IAAI,yBAAyB,CAAC,OAAO;gBAAE,OAAO;YAC9C,yBAAyB,CAAC,OAAO;gBAC/B,oBAAoB,EAAE,sBAAsB,EAAE,IAAI,IAAI,CAAC;YACzD,OAAO;QACT,CAAC;QAED,yBAAyB,CAAC,OAAO,EAAE,EAAE,CAAC;QACtC,yBAAyB,CAAC,OAAO,GAAG,IAAI,CAAC;IAC3C,CAAC,EACD,CAAC,oBAAoB,CAAC,CACvB,CAAC;IAEF,MAAM,gBAAgB,GAAG,WAAW,CAClC,CAAC,QAAiB,EAAE,EAAE;QACpB,IAAI,CAAC,YAAY,EAAE,CAAC;YAClB,kBAAkB,CAAC,QAAQ,CAAC,CAAC;QAC/B,CAAC;QACD,YAAY,EAAE,CAAC,QAAQ,CAAC,CAAC;IAC3B,CAAC,EACD,CAAC,YAAY,EAAE,kBAAkB,EAAE,YAAY,CAAC,CACjD,CAAC;IAEF,SAAS,CAAC,GAAG,EAAE;QACb,IAAI,CAAC,YAAY;YAAE,OAAO;QAC1B,kBAAkB,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC;IACpC,CAAC,EAAE,CAAC,YAAY,EAAE,IAAI,EAAE,kBAAkB,CAAC,CAAC,CAAC;IAE7C,SAAS,CAAC,GAAG,EAAE;QACb,OAAO,GAAG,EAAE;YACV,yBAAyB,CAAC,OAAO,EAAE,EAAE,CAAC;YACtC,yBAAyB,CAAC,OAAO,GAAG,IAAI,CAAC;QAC3C,CAAC,CAAC;IACJ,CAAC,EAAE,EAAE,CAAC,CAAC;IAEP,OAAO,CACL,KAAC,qBAAqB,CAAC,IAAI,OACrB,KAAK,KACL,IAAI,KACJ,CAAC,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,EAC1C,YAAY,EAAE,gBAAgB,GAC9B,CACH,CAAC;AACJ,CAAC,CAAC;AAEF,0BAA0B,CAAC,WAAW,GAAG,6BAA6B,CAAC"}
@@ -0,0 +1,2 @@
1
+ export declare const isJSONValueEqual: (a: unknown, b: unknown) => boolean;
2
+ //# sourceMappingURL=is-json-equal.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"is-json-equal.d.ts","sourceRoot":"","sources":["../../../src/utils/json/is-json-equal.ts"],"names":[],"mappings":"AA4CA,eAAO,MAAM,gBAAgB,GAAI,GAAG,OAAO,EAAE,GAAG,OAAO,KAAG,OAGzD,CAAC"}
@@ -0,0 +1,31 @@
1
+ import { isJSONValue, isRecord } from "./is-json.js";
2
+ const MAX_JSON_DEPTH = 100;
3
+ const isJSONValueEqualAtDepth = (a, b, currentDepth) => {
4
+ if (a === b)
5
+ return true;
6
+ if (currentDepth > MAX_JSON_DEPTH)
7
+ return false;
8
+ if (a == null || b == null)
9
+ return false;
10
+ if (Array.isArray(a)) {
11
+ if (!Array.isArray(b) || a.length !== b.length)
12
+ return false;
13
+ return a.every((item, index) => isJSONValueEqualAtDepth(item, b[index], currentDepth + 1));
14
+ }
15
+ if (Array.isArray(b))
16
+ return false;
17
+ if (!isRecord(a) || !isRecord(b))
18
+ return false;
19
+ const aKeys = Object.keys(a);
20
+ const bKeys = Object.keys(b);
21
+ if (aKeys.length !== bKeys.length)
22
+ return false;
23
+ return aKeys.every((key) => Object.hasOwn(b, key) &&
24
+ isJSONValueEqualAtDepth(a[key], b[key], currentDepth + 1));
25
+ };
26
+ export const isJSONValueEqual = (a, b) => {
27
+ if (!isJSONValue(a) || !isJSONValue(b))
28
+ return false;
29
+ return isJSONValueEqualAtDepth(a, b, 0);
30
+ };
31
+ //# sourceMappingURL=is-json-equal.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"is-json-equal.js","sourceRoot":"","sources":["../../../src/utils/json/is-json-equal.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,WAAW,EAAE,QAAQ,EAAE,qBAAkB;AAElD,MAAM,cAAc,GAAG,GAAG,CAAC;AAE3B,MAAM,uBAAuB,GAAG,CAC9B,CAAoB,EACpB,CAAoB,EACpB,YAAoB,EACX,EAAE;IACX,IAAI,CAAC,KAAK,CAAC;QAAE,OAAO,IAAI,CAAC;IACzB,IAAI,YAAY,GAAG,cAAc;QAAE,OAAO,KAAK,CAAC;IAEhD,IAAI,CAAC,IAAI,IAAI,IAAI,CAAC,IAAI,IAAI;QAAE,OAAO,KAAK,CAAC;IAEzC,IAAI,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC;QACrB,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,MAAM,KAAK,CAAC,CAAC,MAAM;YAAE,OAAO,KAAK,CAAC;QAC7D,OAAO,CAAC,CAAC,KAAK,CAAC,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,CAC7B,uBAAuB,CACrB,IAAI,EACJ,CAAC,CAAC,KAAK,CAAsB,EAC7B,YAAY,GAAG,CAAC,CACjB,CACF,CAAC;IACJ,CAAC;IAED,IAAI,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC;QAAE,OAAO,KAAK,CAAC;IACnC,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC;QAAE,OAAO,KAAK,CAAC;IAE/C,MAAM,KAAK,GAAG,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAC7B,MAAM,KAAK,GAAG,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAC7B,IAAI,KAAK,CAAC,MAAM,KAAK,KAAK,CAAC,MAAM;QAAE,OAAO,KAAK,CAAC;IAEhD,OAAO,KAAK,CAAC,KAAK,CAChB,CAAC,GAAG,EAAE,EAAE,CACN,MAAM,CAAC,MAAM,CAAC,CAAC,EAAE,GAAG,CAAC;QACrB,uBAAuB,CACrB,CAAC,CAAC,GAAG,CAAsB,EAC3B,CAAC,CAAC,GAAG,CAAsB,EAC3B,YAAY,GAAG,CAAC,CACjB,CACJ,CAAC;AACJ,CAAC,CAAC;AAEF,MAAM,CAAC,MAAM,gBAAgB,GAAG,CAAC,CAAU,EAAE,CAAU,EAAW,EAAE;IAClE,IAAI,CAAC,WAAW,CAAC,CAAC,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC,CAAC;QAAE,OAAO,KAAK,CAAC;IACrD,OAAO,uBAAuB,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC;AAC1C,CAAC,CAAC"}
@@ -1,4 +1,5 @@
1
1
  import { ReadonlyJSONArray, ReadonlyJSONObject, ReadonlyJSONValue } from "assistant-stream/utils";
2
+ export declare function isRecord(value: unknown): value is Record<string, unknown>;
2
3
  export declare function isJSONValue(value: unknown, currentDepth?: number): value is ReadonlyJSONValue;
3
4
  export declare function isJSONArray(value: unknown): value is ReadonlyJSONArray;
4
5
  export declare function isJSONObject(value: unknown): value is ReadonlyJSONObject;
@@ -1 +1 @@
1
- {"version":3,"file":"is-json.d.ts","sourceRoot":"","sources":["../../../src/utils/json/is-json.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,iBAAiB,EACjB,kBAAkB,EAClB,iBAAiB,EAClB,MAAM,wBAAwB,CAAC;AAEhC,wBAAgB,WAAW,CACzB,KAAK,EAAE,OAAO,EACd,YAAY,GAAE,MAAU,GACvB,KAAK,IAAI,iBAAiB,CA+B5B;AAED,wBAAgB,WAAW,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,iBAAiB,CAEtE;AAED,wBAAgB,YAAY,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,kBAAkB,CAQxE"}
1
+ {"version":3,"file":"is-json.d.ts","sourceRoot":"","sources":["../../../src/utils/json/is-json.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,iBAAiB,EACjB,kBAAkB,EAClB,iBAAiB,EAClB,MAAM,wBAAwB,CAAC;AAEhC,wBAAgB,QAAQ,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAEzE;AAED,wBAAgB,WAAW,CACzB,KAAK,EAAE,OAAO,EACd,YAAY,GAAE,MAAU,GACvB,KAAK,IAAI,iBAAiB,CA+B5B;AAED,wBAAgB,WAAW,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,iBAAiB,CAEtE;AAED,wBAAgB,YAAY,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,kBAAkB,CAOxE"}
@@ -1,3 +1,6 @@
1
+ export function isRecord(value) {
2
+ return value != null && typeof value === "object" && !Array.isArray(value);
3
+ }
1
4
  export function isJSONValue(value, currentDepth = 0) {
2
5
  // Protect against too deep recursion
3
6
  if (currentDepth > 100) {
@@ -15,7 +18,7 @@ export function isJSONValue(value, currentDepth = 0) {
15
18
  if (Array.isArray(value)) {
16
19
  return value.every((item) => isJSONValue(item, currentDepth + 1));
17
20
  }
18
- if (typeof value === "object") {
21
+ if (isRecord(value)) {
19
22
  return Object.entries(value).every(([key, val]) => typeof key === "string" && isJSONValue(val, currentDepth + 1));
20
23
  }
21
24
  return false;
@@ -24,8 +27,7 @@ export function isJSONArray(value) {
24
27
  return Array.isArray(value) && value.every(isJSONValue);
25
28
  }
26
29
  export function isJSONObject(value) {
27
- return (value != null &&
28
- typeof value === "object" &&
30
+ return (isRecord(value) &&
29
31
  Object.entries(value).every(([key, val]) => typeof key === "string" && isJSONValue(val)));
30
32
  }
31
33
  //# sourceMappingURL=is-json.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"is-json.js","sourceRoot":"","sources":["../../../src/utils/json/is-json.ts"],"names":[],"mappings":"AAMA,MAAM,UAAU,WAAW,CACzB,KAAc,EACd,eAAuB,CAAC;IAExB,qCAAqC;IACrC,IAAI,YAAY,GAAG,GAAG,EAAE,CAAC;QACvB,OAAO,KAAK,CAAC;IACf,CAAC;IAED,IACE,KAAK,KAAK,IAAI;QACd,OAAO,KAAK,KAAK,QAAQ;QACzB,OAAO,KAAK,KAAK,SAAS,EAC1B,CAAC;QACD,OAAO,IAAI,CAAC;IACd,CAAC;IAED,8BAA8B;IAC9B,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;QAC9B,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,IAAI,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;IACxD,CAAC;IAED,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;QACzB,OAAO,KAAK,CAAC,KAAK,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,WAAW,CAAC,IAAI,EAAE,YAAY,GAAG,CAAC,CAAC,CAAC,CAAC;IACpE,CAAC;IAED,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;QAC9B,OAAO,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,KAAK,CAChC,CAAC,CAAC,GAAG,EAAE,GAAG,CAAC,EAAE,EAAE,CACb,OAAO,GAAG,KAAK,QAAQ,IAAI,WAAW,CAAC,GAAG,EAAE,YAAY,GAAG,CAAC,CAAC,CAChE,CAAC;IACJ,CAAC;IAED,OAAO,KAAK,CAAC;AACf,CAAC;AAED,MAAM,UAAU,WAAW,CAAC,KAAc;IACxC,OAAO,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,IAAI,KAAK,CAAC,KAAK,CAAC,WAAW,CAAC,CAAC;AAC1D,CAAC;AAED,MAAM,UAAU,YAAY,CAAC,KAAc;IACzC,OAAO,CACL,KAAK,IAAI,IAAI;QACb,OAAO,KAAK,KAAK,QAAQ;QACzB,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,KAAK,CACzB,CAAC,CAAC,GAAG,EAAE,GAAG,CAAC,EAAE,EAAE,CAAC,OAAO,GAAG,KAAK,QAAQ,IAAI,WAAW,CAAC,GAAG,CAAC,CAC5D,CACF,CAAC;AACJ,CAAC"}
1
+ {"version":3,"file":"is-json.js","sourceRoot":"","sources":["../../../src/utils/json/is-json.ts"],"names":[],"mappings":"AAMA,MAAM,UAAU,QAAQ,CAAC,KAAc;IACrC,OAAO,KAAK,IAAI,IAAI,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;AAC7E,CAAC;AAED,MAAM,UAAU,WAAW,CACzB,KAAc,EACd,eAAuB,CAAC;IAExB,qCAAqC;IACrC,IAAI,YAAY,GAAG,GAAG,EAAE,CAAC;QACvB,OAAO,KAAK,CAAC;IACf,CAAC;IAED,IACE,KAAK,KAAK,IAAI;QACd,OAAO,KAAK,KAAK,QAAQ;QACzB,OAAO,KAAK,KAAK,SAAS,EAC1B,CAAC;QACD,OAAO,IAAI,CAAC;IACd,CAAC;IAED,8BAA8B;IAC9B,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;QAC9B,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,IAAI,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;IACxD,CAAC;IAED,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;QACzB,OAAO,KAAK,CAAC,KAAK,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,WAAW,CAAC,IAAI,EAAE,YAAY,GAAG,CAAC,CAAC,CAAC,CAAC;IACpE,CAAC;IAED,IAAI,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC;QACpB,OAAO,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,KAAK,CAChC,CAAC,CAAC,GAAG,EAAE,GAAG,CAAC,EAAE,EAAE,CACb,OAAO,GAAG,KAAK,QAAQ,IAAI,WAAW,CAAC,GAAG,EAAE,YAAY,GAAG,CAAC,CAAC,CAChE,CAAC;IACJ,CAAC;IAED,OAAO,KAAK,CAAC;AACf,CAAC;AAED,MAAM,UAAU,WAAW,CAAC,KAAc;IACxC,OAAO,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,IAAI,KAAK,CAAC,KAAK,CAAC,WAAW,CAAC,CAAC;AAC1D,CAAC;AAED,MAAM,UAAU,YAAY,CAAC,KAAc;IACzC,OAAO,CACL,QAAQ,CAAC,KAAK,CAAC;QACf,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,KAAK,CACzB,CAAC,CAAC,GAAG,EAAE,GAAG,CAAC,EAAE,EAAE,CAAC,OAAO,GAAG,KAAK,QAAQ,IAAI,WAAW,CAAC,GAAG,CAAC,CAC5D,CACF,CAAC;AACJ,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@assistant-ui/react",
3
- "version": "0.12.14",
3
+ "version": "0.12.15",
4
4
  "description": "TypeScript/React library for AI Chat",
5
5
  "keywords": [
6
6
  "radix-ui",
@@ -49,19 +49,19 @@
49
49
  ],
50
50
  "sideEffects": false,
51
51
  "dependencies": {
52
- "assistant-cloud": "^0.1.20",
53
52
  "@assistant-ui/core": "^0.1.2",
54
- "@assistant-ui/tap": "^0.5.1",
55
- "@assistant-ui/store": "^0.2.1",
53
+ "@assistant-ui/store": "^0.2.2",
54
+ "@assistant-ui/tap": "^0.5.2",
56
55
  "@radix-ui/primitive": "^1.1.3",
57
56
  "@radix-ui/react-compose-refs": "^1.1.2",
58
57
  "@radix-ui/react-context": "^1.1.3",
59
58
  "@radix-ui/react-primitive": "^2.1.4",
60
59
  "@radix-ui/react-use-callback-ref": "^1.1.1",
61
60
  "@radix-ui/react-use-escape-keydown": "^1.1.1",
62
- "radix-ui": "^1.4.3",
61
+ "assistant-cloud": "^0.1.21",
63
62
  "assistant-stream": "^0.3.4",
64
63
  "nanoid": "^5.1.6",
64
+ "radix-ui": "^1.4.3",
65
65
  "react-textarea-autosize": "^8.5.9",
66
66
  "zod": "^4.3.6",
67
67
  "zustand": "^5.0.11"
@@ -83,7 +83,7 @@
83
83
  "devDependencies": {
84
84
  "@testing-library/react": "^16.3.2",
85
85
  "@types/json-schema": "^7.0.15",
86
- "@types/node": "^25.3.0",
86
+ "@types/node": "^25.3.3",
87
87
  "@types/react": "^19.2.14",
88
88
  "@types/react-dom": "^19.2.3",
89
89
  "jsdom": "^28.1.0",
@@ -6,18 +6,20 @@ import { act, renderHook, waitFor } from "@testing-library/react";
6
6
  import { describe, expect, it, vi } from "vitest";
7
7
  import type { AssistantTransportState } from "./types";
8
8
  import { ToolExecutionStatus, useToolInvocations } from "./useToolInvocations";
9
- import { ReadonlyJSONObject } from "assistant-stream/utils";
9
+ import { ReadonlyJSONObject, ReadonlyJSONValue } from "assistant-stream/utils";
10
10
 
11
11
  const createState = (
12
12
  messages: ThreadAssistantMessage[],
13
+ isRunning: boolean = true,
13
14
  ): AssistantTransportState => ({
14
15
  messages,
15
- isRunning: true,
16
+ isRunning,
16
17
  });
17
18
 
18
19
  const createAssistantMessage = (
19
20
  argsText: string,
20
21
  args: Record<string, unknown>,
22
+ options?: { result?: ReadonlyJSONValue; isError?: boolean },
21
23
  ): ThreadAssistantMessage => ({
22
24
  id: "m-1",
23
25
  role: "assistant",
@@ -37,6 +39,8 @@ const createAssistantMessage = (
37
39
  toolName: "weatherSearch",
38
40
  args: args as ReadonlyJSONObject,
39
41
  argsText,
42
+ ...(options?.result !== undefined && { result: options.result }),
43
+ ...(options?.isError !== undefined && { isError: options.isError }),
40
44
  },
41
45
  ],
42
46
  });
@@ -223,4 +227,223 @@ describe("useToolInvocations", () => {
223
227
  });
224
228
  expect(Object.keys(statuses)).not.toContain("tool-1:rewrite:0");
225
229
  });
230
+
231
+ it("does not close args stream early for non-executable tool snapshots", () => {
232
+ const getTools = () => ({
233
+ weatherSearch: {
234
+ parameters: { type: "object", properties: {} },
235
+ } satisfies Tool,
236
+ });
237
+ const onResult = vi.fn();
238
+ const setToolStatuses = vi.fn();
239
+ const warnSpy = vi.spyOn(console, "warn").mockImplementation(() => {});
240
+
241
+ try {
242
+ const { rerender } = renderHook(
243
+ ({ state }: { state: AssistantTransportState }) =>
244
+ useToolInvocations({
245
+ state,
246
+ getTools,
247
+ onResult,
248
+ setToolStatuses,
249
+ }),
250
+ {
251
+ initialProps: {
252
+ state: createState([]),
253
+ },
254
+ },
255
+ );
256
+
257
+ act(() => {
258
+ rerender({
259
+ state: createState([createAssistantMessage("{}", {})]),
260
+ });
261
+ });
262
+
263
+ act(() => {
264
+ rerender({
265
+ state: createState([
266
+ createAssistantMessage('{"title":"Weekly"', {
267
+ title: "Weekly",
268
+ }),
269
+ ]),
270
+ });
271
+ });
272
+
273
+ act(() => {
274
+ rerender({
275
+ state: createState([
276
+ createAssistantMessage('{"title":"Weekly","columns":["name"]}', {
277
+ title: "Weekly",
278
+ columns: ["name"],
279
+ }),
280
+ ]),
281
+ });
282
+ });
283
+
284
+ expect(warnSpy).not.toHaveBeenCalledWith(
285
+ "argsText updated after controller was closed:",
286
+ expect.anything(),
287
+ );
288
+ expect(warnSpy).not.toHaveBeenCalledWith(
289
+ "argsText updated after controller was closed, restarting tool args stream:",
290
+ expect.anything(),
291
+ );
292
+ expect(onResult).not.toHaveBeenCalled();
293
+ } finally {
294
+ warnSpy.mockRestore();
295
+ }
296
+ });
297
+
298
+ it("closes non-executable complete args stream after run settles", () => {
299
+ const getTools = () => ({
300
+ weatherSearch: {
301
+ parameters: { type: "object", properties: {} },
302
+ } satisfies Tool,
303
+ });
304
+ const onResult = vi.fn();
305
+ const setToolStatuses = vi.fn();
306
+ const warnSpy = vi.spyOn(console, "warn").mockImplementation(() => {});
307
+
308
+ try {
309
+ const { rerender } = renderHook(
310
+ ({ state }: { state: AssistantTransportState }) =>
311
+ useToolInvocations({
312
+ state,
313
+ getTools,
314
+ onResult,
315
+ setToolStatuses,
316
+ }),
317
+ {
318
+ initialProps: {
319
+ state: createState([]),
320
+ },
321
+ },
322
+ );
323
+
324
+ act(() => {
325
+ rerender({
326
+ state: createState(
327
+ [
328
+ createAssistantMessage('{"title":"Weekly"}', {
329
+ title: "Weekly",
330
+ }),
331
+ ],
332
+ true,
333
+ ),
334
+ });
335
+ });
336
+
337
+ act(() => {
338
+ rerender({
339
+ state: createState(
340
+ [
341
+ createAssistantMessage('{"title":"Weekly"}', {
342
+ title: "Weekly",
343
+ }),
344
+ ],
345
+ false,
346
+ ),
347
+ });
348
+ });
349
+
350
+ act(() => {
351
+ rerender({
352
+ state: createState(
353
+ [
354
+ createAssistantMessage('{"title":"Weekly","columns":["name"]}', {
355
+ title: "Weekly",
356
+ columns: ["name"],
357
+ }),
358
+ ],
359
+ false,
360
+ ),
361
+ });
362
+ });
363
+
364
+ expect(warnSpy).toHaveBeenCalledWith(
365
+ "argsText updated after controller was closed, restarting tool args stream:",
366
+ expect.objectContaining({
367
+ previous: '{"title":"Weekly"}',
368
+ next: '{"title":"Weekly","columns":["name"]}',
369
+ }),
370
+ );
371
+ expect(onResult).not.toHaveBeenCalled();
372
+ } finally {
373
+ warnSpy.mockRestore();
374
+ }
375
+ });
376
+
377
+ it("handles backend result when equivalent complete argsText reorders keys", async () => {
378
+ let resolveExecute: ((value: unknown) => void) | undefined;
379
+ const execute = vi.fn(
380
+ () =>
381
+ new Promise<unknown>((resolve) => {
382
+ resolveExecute = resolve;
383
+ }),
384
+ );
385
+ const getTools = () => ({
386
+ weatherSearch: {
387
+ parameters: { type: "object", properties: {} },
388
+ execute,
389
+ } satisfies Tool,
390
+ });
391
+ const onResult = vi.fn();
392
+ const setToolStatuses = vi.fn();
393
+
394
+ const { rerender } = renderHook(
395
+ ({ state }: { state: AssistantTransportState }) =>
396
+ useToolInvocations({
397
+ state,
398
+ getTools,
399
+ onResult,
400
+ setToolStatuses,
401
+ }),
402
+ {
403
+ initialProps: {
404
+ state: createState([]),
405
+ },
406
+ },
407
+ );
408
+
409
+ act(() => {
410
+ rerender({
411
+ state: createState([
412
+ createAssistantMessage('{"a":1,"b":2}', {
413
+ a: 1,
414
+ b: 2,
415
+ }),
416
+ ]),
417
+ });
418
+ });
419
+
420
+ await waitFor(() => {
421
+ expect(execute).toHaveBeenCalledTimes(1);
422
+ });
423
+
424
+ act(() => {
425
+ rerender({
426
+ state: createState([
427
+ createAssistantMessage(
428
+ '{"b":2,"a":1}',
429
+ {
430
+ a: 1,
431
+ b: 2,
432
+ },
433
+ {
434
+ result: { source: "backend" },
435
+ },
436
+ ),
437
+ ]),
438
+ });
439
+ });
440
+
441
+ await act(async () => {
442
+ resolveExecute?.({ source: "client" });
443
+ });
444
+
445
+ await waitFor(() => {
446
+ expect(onResult).not.toHaveBeenCalled();
447
+ });
448
+ });
226
449
  });