@assistant-ui/react-devtools 1.2.19 → 1.2.21

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 (213) hide show
  1. package/dist/DevToolsModal.d.ts +10 -13
  2. package/dist/DevToolsModal.d.ts.map +1 -1
  3. package/dist/DevToolsModalImpl.d.ts +3 -5
  4. package/dist/DevToolsModalImpl.d.ts.map +1 -1
  5. package/dist/data/createInProcessClient.d.ts +3 -6
  6. package/dist/data/createInProcessClient.d.ts.map +1 -1
  7. package/dist/data/projectApi.d.ts +6 -8
  8. package/dist/data/projectApi.d.ts.map +1 -1
  9. package/dist/data/projectApi.js +2 -2
  10. package/dist/data/projectApi.js.map +1 -1
  11. package/dist/data/types.d.ts +33 -35
  12. package/dist/data/types.d.ts.map +1 -1
  13. package/dist/data/useDevToolsClient.d.ts +7 -10
  14. package/dist/data/useDevToolsClient.d.ts.map +1 -1
  15. package/dist/index.d.ts +10 -10
  16. package/dist/index.d.ts.map +1 -0
  17. package/dist/shell/DevToolsOverlay.d.ts +7 -10
  18. package/dist/shell/DevToolsOverlay.d.ts.map +1 -1
  19. package/dist/shell/DevToolsPanel.d.ts +12 -15
  20. package/dist/shell/DevToolsPanel.d.ts.map +1 -1
  21. package/dist/shell/ShadowRoot.d.ts +7 -10
  22. package/dist/shell/ShadowRoot.d.ts.map +1 -1
  23. package/dist/shell/SplitLayout.d.ts +13 -16
  24. package/dist/shell/SplitLayout.d.ts.map +1 -1
  25. package/dist/shell/registry.d.ts +34 -37
  26. package/dist/shell/registry.d.ts.map +1 -1
  27. package/dist/shell/tabs/ActivityTab.d.ts +2 -5
  28. package/dist/shell/tabs/ActivityTab.d.ts.map +1 -1
  29. package/dist/shell/tabs/ContextTab.d.ts +2 -5
  30. package/dist/shell/tabs/ContextTab.d.ts.map +1 -1
  31. package/dist/shell/tabs/RawTab.d.ts +2 -5
  32. package/dist/shell/tabs/RawTab.d.ts.map +1 -1
  33. package/dist/shell/tabs/ThreadTab.d.ts +2 -5
  34. package/dist/shell/tabs/ThreadTab.d.ts.map +1 -1
  35. package/dist/shell/tabs/navSelection.d.ts +1 -4
  36. package/dist/shell/tabs/navSelection.d.ts.map +1 -1
  37. package/dist/styles/panel.generated.d.ts +1 -4
  38. package/dist/styles/panel.generated.d.ts.map +1 -1
  39. package/dist/types.d.ts +6 -9
  40. package/dist/types.d.ts.map +1 -1
  41. package/dist/utils/common.d.ts +13 -16
  42. package/dist/utils/common.d.ts.map +1 -1
  43. package/dist/utils/diff.d.ts +6 -9
  44. package/dist/utils/diff.d.ts.map +1 -1
  45. package/dist/utils/serialization.d.ts +7 -10
  46. package/dist/utils/serialization.d.ts.map +1 -1
  47. package/dist/utils/serialization.js +17 -0
  48. package/dist/utils/serialization.js.map +1 -1
  49. package/dist/utils/toolNormalization.d.ts +14 -17
  50. package/dist/utils/toolNormalization.d.ts.map +1 -1
  51. package/dist/utils/unserializable.d.ts +2 -5
  52. package/dist/utils/unserializable.d.ts.map +1 -1
  53. package/dist/views/activity/EventLogRow.d.ts +12 -15
  54. package/dist/views/activity/EventLogRow.d.ts.map +1 -1
  55. package/dist/views/activity/EventStreamPane.d.ts +3 -6
  56. package/dist/views/activity/EventStreamPane.d.ts.map +1 -1
  57. package/dist/views/activity/OrphansPane.d.ts +3 -6
  58. package/dist/views/activity/OrphansPane.d.ts.map +1 -1
  59. package/dist/views/activity/RunDetailPane.d.ts +3 -6
  60. package/dist/views/activity/RunDetailPane.d.ts.map +1 -1
  61. package/dist/views/activity/activityNodes.d.ts +29 -32
  62. package/dist/views/activity/activityNodes.d.ts.map +1 -1
  63. package/dist/views/activity/index.d.ts +7 -6
  64. package/dist/views/activity/index.d.ts.map +1 -0
  65. package/dist/views/attachments/AttachmentList.d.ts +5 -8
  66. package/dist/views/attachments/AttachmentList.d.ts.map +1 -1
  67. package/dist/views/attachments/MediaPartView.d.ts +10 -13
  68. package/dist/views/attachments/MediaPartView.d.ts.map +1 -1
  69. package/dist/views/attachments/formatBytes.d.ts +3 -6
  70. package/dist/views/attachments/formatBytes.d.ts.map +1 -1
  71. package/dist/views/attachments/index.d.ts +5 -5
  72. package/dist/views/attachments/index.d.ts.map +1 -0
  73. package/dist/views/attachments/parseAttachment.d.ts +3 -6
  74. package/dist/views/attachments/parseAttachment.d.ts.map +1 -1
  75. package/dist/views/attachments/types.d.ts +12 -15
  76. package/dist/views/attachments/types.d.ts.map +1 -1
  77. package/dist/views/context/ContextDetail.d.ts +5 -8
  78. package/dist/views/context/ContextDetail.d.ts.map +1 -1
  79. package/dist/views/context/ToolUIsView.d.ts +2 -5
  80. package/dist/views/context/ToolUIsView.d.ts.map +1 -1
  81. package/dist/views/context/contextNodes.d.ts +21 -24
  82. package/dist/views/context/contextNodes.d.ts.map +1 -1
  83. package/dist/views/context/index.d.ts +5 -4
  84. package/dist/views/context/index.d.ts.map +1 -0
  85. package/dist/views/mcp/McpView.d.ts +3 -6
  86. package/dist/views/mcp/McpView.d.ts.map +1 -1
  87. package/dist/views/mcp/index.d.ts +2 -2
  88. package/dist/views/mcp/index.d.ts.map +1 -0
  89. package/dist/views/mcp/parse.d.ts +2 -5
  90. package/dist/views/mcp/parse.d.ts.map +1 -1
  91. package/dist/views/mcp/types.d.ts +15 -18
  92. package/dist/views/mcp/types.d.ts.map +1 -1
  93. package/dist/views/message/ChainOfThought.d.ts +3 -6
  94. package/dist/views/message/ChainOfThought.d.ts.map +1 -1
  95. package/dist/views/message/MessageDetailPane.d.ts +4 -7
  96. package/dist/views/message/MessageDetailPane.d.ts.map +1 -1
  97. package/dist/views/message/MessageItem.d.ts +5 -8
  98. package/dist/views/message/MessageItem.d.ts.map +1 -1
  99. package/dist/views/message/MessageList.d.ts +4 -7
  100. package/dist/views/message/MessageList.d.ts.map +1 -1
  101. package/dist/views/message/MessageMetrics.d.ts +3 -6
  102. package/dist/views/message/MessageMetrics.d.ts.map +1 -1
  103. package/dist/views/message/MessageTimeline.d.ts +7 -10
  104. package/dist/views/message/MessageTimeline.d.ts.map +1 -1
  105. package/dist/views/message/PartDisclosure.d.ts +6 -9
  106. package/dist/views/message/PartDisclosure.d.ts.map +1 -1
  107. package/dist/views/message/PartView.d.ts +4 -7
  108. package/dist/views/message/PartView.d.ts.map +1 -1
  109. package/dist/views/message/RoleLabel.d.ts +3 -6
  110. package/dist/views/message/RoleLabel.d.ts.map +1 -1
  111. package/dist/views/message/StatusBadge.d.ts +7 -10
  112. package/dist/views/message/StatusBadge.d.ts.map +1 -1
  113. package/dist/views/message/ToolCallView.d.ts +4 -7
  114. package/dist/views/message/ToolCallView.d.ts.map +1 -1
  115. package/dist/views/message/TtftBar.d.ts +3 -6
  116. package/dist/views/message/TtftBar.d.ts.map +1 -1
  117. package/dist/views/message/index.d.ts +6 -6
  118. package/dist/views/message/index.d.ts.map +1 -0
  119. package/dist/views/message/messagePlainText.d.ts +2 -5
  120. package/dist/views/message/messagePlainText.d.ts.map +1 -1
  121. package/dist/views/message/parse.d.ts +5 -8
  122. package/dist/views/message/parse.d.ts.map +1 -1
  123. package/dist/views/message/types.d.ts +108 -110
  124. package/dist/views/message/types.d.ts.map +1 -1
  125. package/dist/views/model-context/ModelContextView.d.ts +9 -12
  126. package/dist/views/model-context/ModelContextView.d.ts.map +1 -1
  127. package/dist/views/nav.d.ts +10 -13
  128. package/dist/views/nav.d.ts.map +1 -1
  129. package/dist/views/raw/RawDetail.d.ts +5 -8
  130. package/dist/views/raw/RawDetail.d.ts.map +1 -1
  131. package/dist/views/raw/index.d.ts +4 -3
  132. package/dist/views/raw/index.d.ts.map +1 -0
  133. package/dist/views/raw/rawNodes.d.ts +13 -16
  134. package/dist/views/raw/rawNodes.d.ts.map +1 -1
  135. package/dist/views/raw/statePreview.d.ts +1 -4
  136. package/dist/views/raw/statePreview.d.ts.map +1 -1
  137. package/dist/views/runs/PayloadDiff.d.ts +3 -6
  138. package/dist/views/runs/PayloadDiff.d.ts.map +1 -1
  139. package/dist/views/runs/parse.d.ts +3 -6
  140. package/dist/views/runs/parse.d.ts.map +1 -1
  141. package/dist/views/runs/types.d.ts +18 -21
  142. package/dist/views/runs/types.d.ts.map +1 -1
  143. package/dist/views/scopes/ScopesView.d.ts +2 -5
  144. package/dist/views/scopes/ScopesView.d.ts.map +1 -1
  145. package/dist/views/scopes/index.d.ts +2 -2
  146. package/dist/views/scopes/index.d.ts.map +1 -0
  147. package/dist/views/scopes/parse.d.ts +2 -5
  148. package/dist/views/scopes/parse.d.ts.map +1 -1
  149. package/dist/views/scopes/types.d.ts +5 -8
  150. package/dist/views/scopes/types.d.ts.map +1 -1
  151. package/dist/views/thread/ComposerAttachments.d.ts +3 -6
  152. package/dist/views/thread/ComposerAttachments.d.ts.map +1 -1
  153. package/dist/views/thread/ComposerFlags.d.ts +3 -6
  154. package/dist/views/thread/ComposerFlags.d.ts.map +1 -1
  155. package/dist/views/thread/ComposerQueue.d.ts +3 -6
  156. package/dist/views/thread/ComposerQueue.d.ts.map +1 -1
  157. package/dist/views/thread/ConversationList.d.ts +7 -10
  158. package/dist/views/thread/ConversationList.d.ts.map +1 -1
  159. package/dist/views/thread/ConversationStatusDot.d.ts +12 -15
  160. package/dist/views/thread/ConversationStatusDot.d.ts.map +1 -1
  161. package/dist/views/thread/ThreadDetails.d.ts +4 -7
  162. package/dist/views/thread/ThreadDetails.d.ts.map +1 -1
  163. package/dist/views/thread/Transcript.d.ts +8 -11
  164. package/dist/views/thread/Transcript.d.ts.map +1 -1
  165. package/dist/views/thread/index.d.ts +10 -10
  166. package/dist/views/thread/index.d.ts.map +1 -0
  167. package/dist/views/thread/resolve.d.ts +3 -6
  168. package/dist/views/thread/resolve.d.ts.map +1 -1
  169. package/dist/views/thread/types.d.ts +40 -42
  170. package/dist/views/thread/types.d.ts.map +1 -1
  171. package/dist/views/thread/utils.d.ts +5 -8
  172. package/dist/views/thread/utils.d.ts.map +1 -1
  173. package/dist/views/ui/CenteredMessage.d.ts +3 -6
  174. package/dist/views/ui/CenteredMessage.d.ts.map +1 -1
  175. package/dist/views/ui/Chip.d.ts +4 -7
  176. package/dist/views/ui/Chip.d.ts.map +1 -1
  177. package/dist/views/ui/ControlButton.d.ts +2 -5
  178. package/dist/views/ui/ControlButton.d.ts.map +1 -1
  179. package/dist/views/ui/CopyButton.d.ts +7 -10
  180. package/dist/views/ui/CopyButton.d.ts.map +1 -1
  181. package/dist/views/ui/EmptyState.d.ts +3 -6
  182. package/dist/views/ui/EmptyState.d.ts.map +1 -1
  183. package/dist/views/ui/InfoCard.d.ts +6 -9
  184. package/dist/views/ui/InfoCard.d.ts.map +1 -1
  185. package/dist/views/ui/JSONTree.d.ts +4 -7
  186. package/dist/views/ui/JSONTree.d.ts.map +1 -1
  187. package/dist/views/ui/PaneHeader.d.ts +8 -11
  188. package/dist/views/ui/PaneHeader.d.ts.map +1 -1
  189. package/dist/views/ui/RailTime.d.ts +3 -6
  190. package/dist/views/ui/RailTime.d.ts.map +1 -1
  191. package/dist/views/ui/SectionLabel.d.ts +3 -6
  192. package/dist/views/ui/SectionLabel.d.ts.map +1 -1
  193. package/dist/views/ui/SelectableRow.d.ts +8 -11
  194. package/dist/views/ui/SelectableRow.d.ts.map +1 -1
  195. package/dist/views/ui/Sparkline.d.ts +3 -6
  196. package/dist/views/ui/Sparkline.d.ts.map +1 -1
  197. package/dist/views/ui/SummaryItem.d.ts +6 -9
  198. package/dist/views/ui/SummaryItem.d.ts.map +1 -1
  199. package/dist/views/ui/ToneBadge.d.ts +7 -10
  200. package/dist/views/ui/ToneBadge.d.ts.map +1 -1
  201. package/dist/views/ui/badgeTone.d.ts +13 -16
  202. package/dist/views/ui/badgeTone.d.ts.map +1 -1
  203. package/dist/views/ui/getInlineJson.d.ts +1 -4
  204. package/dist/views/ui/getInlineJson.d.ts.map +1 -1
  205. package/dist/views/ui/index.d.ts +17 -16
  206. package/dist/views/ui/index.d.ts.map +1 -0
  207. package/dist/views/ui/useCopyToClipboard.d.ts +4 -7
  208. package/dist/views/ui/useCopyToClipboard.d.ts.map +1 -1
  209. package/package.json +14 -13
  210. package/src/data/projectApi.test.ts +41 -0
  211. package/src/data/projectApi.ts +2 -2
  212. package/src/utils/serialization.test.ts +81 -0
  213. package/src/utils/serialization.ts +36 -0
@@ -1,16 +1,13 @@
1
- import { ReactNode } from "react";
2
- //#region src/views/ui/InfoCard.d.ts
1
+ import type { ReactNode } from "react";
3
2
  /**
4
3
  * A flat content section: a header row (title + optional count/action) over its
5
4
  * content. No box, no nesting; sections are separated by whitespace on the
6
5
  * panel surface.
7
6
  */
8
- declare const InfoCard: ({ title, count, action, children }: {
9
- title?: ReactNode;
10
- count?: ReactNode;
11
- action?: ReactNode;
12
- children: ReactNode;
7
+ export declare const InfoCard: ({ title, count, action, children, }: {
8
+ title?: ReactNode;
9
+ count?: ReactNode;
10
+ action?: ReactNode;
11
+ children: ReactNode;
13
12
  }) => import("react").JSX.Element;
14
- //#endregion
15
- export { InfoCard };
16
13
  //# sourceMappingURL=InfoCard.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"InfoCard.d.ts","names":[],"sources":["../../../src/views/ui/InfoCard.tsx"],"mappings":";;;;;;;cAOa,aAAY,OAAA,OAAA,QAAA;EAMvB,QAAQ;EACR,QAAQ;EACR,SAAS;EACT,UAAU;sBACX,IAAA"}
1
+ {"version":3,"file":"InfoCard.d.ts","sourceRoot":"","sources":["../../../src/views/ui/InfoCard.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAEvC;;;;GAIG;AACH,eAAO,MAAM,QAAQ,GAAI,qCAKtB;IACD,KAAK,CAAC,EAAE,SAAS,CAAC;IAClB,KAAK,CAAC,EAAE,SAAS,CAAC;IAClB,MAAM,CAAC,EAAE,SAAS,CAAC;IACnB,QAAQ,EAAE,SAAS,CAAC;CACrB,gCAiBA,CAAC"}
@@ -1,12 +1,9 @@
1
- //#region src/views/ui/JSONTree.d.ts
2
1
  /**
3
2
  * Recursive JSON view. `compact` fits tool rows; copy always provides formatted JSON.
4
3
  */
5
- declare const JSONTree: ({ value, openDepth, compact }: {
6
- value: unknown;
7
- openDepth?: number;
8
- compact?: boolean;
4
+ export declare const JSONTree: ({ value, openDepth, compact, }: {
5
+ value: unknown;
6
+ openDepth?: number;
7
+ compact?: boolean;
9
8
  }) => import("react").JSX.Element;
10
- //#endregion
11
- export { JSONTree };
12
9
  //# sourceMappingURL=JSONTree.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"JSONTree.d.ts","names":[],"sources":["../../../src/views/ui/JSONTree.tsx"],"mappings":";;;;cA0Ma,aAAY,OAAA,WAAA;EAKvB;EACA;EACA;sBACD,IAAA"}
1
+ {"version":3,"file":"JSONTree.d.ts","sourceRoot":"","sources":["../../../src/views/ui/JSONTree.tsx"],"names":[],"mappings":"AAuMA;;GAEG;AACH,eAAO,MAAM,QAAQ,GAAI,gCAItB;IACD,KAAK,EAAE,OAAO,CAAC;IACf,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB,gCA8BA,CAAC"}
@@ -1,19 +1,16 @@
1
- import { ReactNode } from "react";
2
- //#region src/views/ui/PaneHeader.d.ts
1
+ import type { ReactNode } from "react";
3
2
  /**
4
3
  * Fixed-height rail header. Every column in a split layout should use this so
5
4
  * titles line up across columns.
6
5
  */
7
- declare const PaneHeader: ({ children, trailing, borderless }: {
8
- children: ReactNode;
9
- trailing?: ReactNode | undefined;
10
- borderless?: boolean;
6
+ export declare const PaneHeader: ({ children, trailing, borderless, }: {
7
+ children: ReactNode;
8
+ trailing?: ReactNode | undefined;
9
+ borderless?: boolean;
11
10
  }) => import("react").JSX.Element;
12
11
  /** Header that grows below a {@link PaneHeader}-height title row. */
13
- declare const PaneHeaderStack: ({ header, children }: {
14
- header: ReactNode;
15
- children?: ReactNode | undefined;
12
+ export declare const PaneHeaderStack: ({ header, children, }: {
13
+ header: ReactNode;
14
+ children?: ReactNode | undefined;
16
15
  }) => import("react").JSX.Element;
17
- //#endregion
18
- export { PaneHeader, PaneHeaderStack };
19
16
  //# sourceMappingURL=PaneHeader.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"PaneHeader.d.ts","names":[],"sources":["../../../src/views/ui/PaneHeader.tsx"],"mappings":";;;;;;cAMa,eAAc,UAAA,UAAA;EAKzB,UAAU;EACV,WAAW;EACX;sBACD,IAAA;;cAoBY,oBAAmB,QAAA;EAI9B,QAAQ;EACR,WAAW;sBACZ,IAAA"}
1
+ {"version":3,"file":"PaneHeader.d.ts","sourceRoot":"","sources":["../../../src/views/ui/PaneHeader.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAEvC;;;GAGG;AACH,eAAO,MAAM,UAAU,GAAI,qCAIxB;IACD,QAAQ,EAAE,SAAS,CAAC;IACpB,QAAQ,CAAC,EAAE,SAAS,GAAG,SAAS,CAAC;IACjC,UAAU,CAAC,EAAE,OAAO,CAAC;CACtB,gCAiBA,CAAC;AAEF,qEAAqE;AACrE,eAAO,MAAM,eAAe,GAAI,uBAG7B;IACD,MAAM,EAAE,SAAS,CAAC;IAClB,QAAQ,CAAC,EAAE,SAAS,GAAG,SAAS,CAAC;CAClC,gCAKA,CAAC"}
@@ -1,9 +1,6 @@
1
- //#region src/views/ui/RailTime.d.ts
2
1
  /** Compact timestamp for narrow rails; full value goes in the tooltip. */
3
- declare const RailTime: ({ value, className }: {
4
- value: string | undefined;
5
- className?: string | undefined;
2
+ export declare const RailTime: ({ value, className, }: {
3
+ value: string | undefined;
4
+ className?: string | undefined;
6
5
  }) => import("react").JSX.Element | null;
7
- //#endregion
8
- export { RailTime };
9
6
  //# sourceMappingURL=RailTime.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"RailTime.d.ts","names":[],"sources":["../../../src/views/ui/RailTime.tsx"],"mappings":";;cAIa,aAAY,OAAA;EAIvB;EACA;sBACD,IAAA"}
1
+ {"version":3,"file":"RailTime.d.ts","sourceRoot":"","sources":["../../../src/views/ui/RailTime.tsx"],"names":[],"mappings":"AAGA,0EAA0E;AAC1E,eAAO,MAAM,QAAQ,GAAI,uBAGtB;IACD,KAAK,EAAE,MAAM,GAAG,SAAS,CAAC;IAC1B,SAAS,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CAChC,uCAgBA,CAAC"}
@@ -1,8 +1,5 @@
1
- import { ReactNode } from "react";
2
- //#region src/views/ui/SectionLabel.d.ts
3
- declare const SectionLabel: ({ children }: {
4
- children: ReactNode;
1
+ import type { ReactNode } from "react";
2
+ export declare const SectionLabel: ({ children }: {
3
+ children: ReactNode;
5
4
  }) => import("react").JSX.Element;
6
- //#endregion
7
- export { SectionLabel };
8
5
  //# sourceMappingURL=SectionLabel.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"SectionLabel.d.ts","names":[],"sources":["../../../src/views/ui/SectionLabel.tsx"],"mappings":";;cAEa,iBAAgB;EAAgB,UAAU;sBAAW,IAAA"}
1
+ {"version":3,"file":"SectionLabel.d.ts","sourceRoot":"","sources":["../../../src/views/ui/SectionLabel.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAEvC,eAAO,MAAM,YAAY,GAAI,cAAc;IAAE,QAAQ,EAAE,SAAS,CAAA;CAAE,gCAIjE,CAAC"}
@@ -1,18 +1,15 @@
1
- import { ReactNode } from "react";
2
- //#region src/views/ui/SelectableRow.d.ts
1
+ import { type ReactNode } from "react";
3
2
  /**
4
3
  * A row in a master rail. The leading border + accent fill mark the current
5
4
  * selection; the whole row is the hit target. Content is free-form so a row can
6
5
  * be a single line or a small block (a transcript turn).
7
6
  */
8
- declare const SelectableRow: import("react").ForwardRefExoticComponent<{
9
- selected?: boolean;
10
- onSelect?: (() => void) | undefined;
11
- className?: string | undefined;
12
- /** Fixed-height row for uniform rails. */
13
- dense?: boolean;
14
- children: ReactNode;
7
+ export declare const SelectableRow: import("react").ForwardRefExoticComponent<{
8
+ selected?: boolean;
9
+ onSelect?: (() => void) | undefined;
10
+ className?: string | undefined;
11
+ /** Fixed-height row for uniform rails. */
12
+ dense?: boolean;
13
+ children: ReactNode;
15
14
  } & import("react").RefAttributes<HTMLButtonElement>>;
16
- //#endregion
17
- export { SelectableRow };
18
15
  //# sourceMappingURL=SelectableRow.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"SelectableRow.d.ts","names":[],"sources":["../../../src/views/ui/SelectableRow.tsx"],"mappings":";;;;;;;cAQa,+BAAa;EAGX;EACA;EACC;;EAEJ;EACE,UAAA;oBAwBZ,cAAA"}
1
+ {"version":3,"file":"SelectableRow.d.ts","sourceRoot":"","sources":["../../../src/views/ui/SelectableRow.tsx"],"names":[],"mappings":"AACA,OAAO,EAAc,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AAEnD;;;;GAIG;AACH,eAAO,MAAM,aAAa;eAGX,OAAO;eACP,CAAC,MAAM,IAAI,CAAC,GAAG,SAAS;gBACvB,MAAM,GAAG,SAAS;IAC9B,0CAA0C;YAClC,OAAO;cACL,SAAS;qDAwBrB,CAAC"}
@@ -1,14 +1,11 @@
1
- //#region src/views/ui/Sparkline.d.ts
2
1
  /**
3
2
  * A monochrome trend line drawn as an SVG polyline. No axes, legend, or
4
3
  * gridlines: it answers "is this metric rising, falling, or spiking" at a
5
4
  * glance and nothing more. Renders nothing below 2 points; color comes from the
6
5
  * surrounding text color (`currentColor`).
7
6
  */
8
- declare const Sparkline: ({ values, className }: {
9
- values: readonly number[];
10
- className?: string;
7
+ export declare const Sparkline: ({ values, className, }: {
8
+ values: readonly number[];
9
+ className?: string;
11
10
  }) => import("react").JSX.Element | null;
12
- //#endregion
13
- export { Sparkline };
14
11
  //# sourceMappingURL=Sparkline.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"Sparkline.d.ts","names":[],"sources":["../../../src/views/ui/Sparkline.tsx"],"mappings":";;;;;;;cAQa,cAAa,QAAA;EAIxB;EACA;sBACD,IAAA"}
1
+ {"version":3,"file":"Sparkline.d.ts","sourceRoot":"","sources":["../../../src/views/ui/Sparkline.tsx"],"names":[],"mappings":"AAEA;;;;;GAKG;AACH,eAAO,MAAM,SAAS,GAAI,wBAGvB;IACD,MAAM,EAAE,SAAS,MAAM,EAAE,CAAC;IAC1B,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,uCAkCA,CAAC"}
@@ -1,17 +1,14 @@
1
- import { ReactNode } from "react";
2
- //#region src/views/ui/SummaryItem.d.ts
1
+ import type { ReactNode } from "react";
3
2
  /**
4
3
  * A key/value row: label left, value right on one baseline. Flat (no box). Group
5
4
  * a sequence in `SummaryList` for hairline dividers.
6
5
  */
7
- declare const SummaryItem: ({ label, value }: {
8
- label: string;
9
- value: ReactNode;
6
+ export declare const SummaryItem: ({ label, value, }: {
7
+ label: string;
8
+ value: ReactNode;
10
9
  }) => import("react").JSX.Element;
11
10
  /** Divided list of `SummaryItem` rows. Unboxed: dividers do the grouping. */
12
- declare const SummaryList: ({ children }: {
13
- children: ReactNode;
11
+ export declare const SummaryList: ({ children }: {
12
+ children: ReactNode;
14
13
  }) => import("react").JSX.Element;
15
- //#endregion
16
- export { SummaryItem, SummaryList };
17
14
  //# sourceMappingURL=SummaryItem.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"SummaryItem.d.ts","names":[],"sources":["../../../src/views/ui/SummaryItem.tsx"],"mappings":";;;;;;cAMa,gBAAe,OAAA;EAI1B;EACA,OAAO;sBACR,IAAA;;cAUY,gBAAe;EAAgB,UAAU;sBAAW,IAAA"}
1
+ {"version":3,"file":"SummaryItem.d.ts","sourceRoot":"","sources":["../../../src/views/ui/SummaryItem.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAEvC;;;GAGG;AACH,eAAO,MAAM,WAAW,GAAI,mBAGzB;IACD,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,SAAS,CAAC;CAClB,gCAOA,CAAC;AAEF,6EAA6E;AAC7E,eAAO,MAAM,WAAW,GAAI,cAAc;IAAE,QAAQ,EAAE,SAAS,CAAA;CAAE,gCAEhE,CAAC"}
@@ -1,12 +1,9 @@
1
- import { BadgeTone } from "./badgeTone.js";
2
- import { ReactNode } from "react";
3
- //#region src/views/ui/ToneBadge.d.ts
4
- declare const ToneBadge: ({ tone, size, className, children }: {
5
- tone?: BadgeTone | undefined;
6
- size?: "default" | "sm";
7
- className?: string | undefined;
8
- children: ReactNode;
1
+ import type { ReactNode } from "react";
2
+ import type { BadgeTone } from "./badgeTone.js";
3
+ export declare const ToneBadge: ({ tone, size, className, children, }: {
4
+ tone?: BadgeTone | undefined;
5
+ size?: "default" | "sm";
6
+ className?: string | undefined;
7
+ children: ReactNode;
9
8
  }) => import("react").JSX.Element;
10
- //#endregion
11
- export { ToneBadge };
12
9
  //# sourceMappingURL=ToneBadge.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"ToneBadge.d.ts","names":[],"sources":["../../../src/views/ui/ToneBadge.tsx"],"mappings":";;;cAKa,cAAa,MAAA,MAAA,WAAA;EAMxB,OAAO;EACP;EACA;EACA,UAAU;sBACX,IAAA"}
1
+ {"version":3,"file":"ToneBadge.d.ts","sourceRoot":"","sources":["../../../src/views/ui/ToneBadge.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAEvC,OAAO,KAAK,EAAE,SAAS,EAAE,uBAAoB;AAE7C,eAAO,MAAM,SAAS,GAAI,sCAKvB;IACD,IAAI,CAAC,EAAE,SAAS,GAAG,SAAS,CAAC;IAC7B,IAAI,CAAC,EAAE,SAAS,GAAG,IAAI,CAAC;IACxB,SAAS,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC/B,QAAQ,EAAE,SAAS,CAAC;CACrB,gCAUA,CAAC"}
@@ -1,21 +1,18 @@
1
- //#region src/views/ui/badgeTone.d.ts
2
- declare const BADGE_TONE: {
3
- readonly blue: "border-blue-300 bg-blue-500/10 text-blue-700 dark:border-blue-500/40 dark:bg-blue-500/15 dark:text-blue-300";
4
- readonly emerald: "border-emerald-300 bg-emerald-500/10 text-emerald-700 dark:border-emerald-500/40 dark:bg-emerald-500/15 dark:text-emerald-300";
5
- readonly amber: "border-amber-300 bg-amber-500/10 text-amber-700 dark:border-amber-500/40 dark:bg-amber-500/15 dark:text-amber-300";
6
- readonly red: "border-red-300 bg-red-500/10 text-red-700 dark:border-red-500/40 dark:bg-red-500/15 dark:text-red-300";
7
- readonly violet: "border-violet-300 bg-violet-500/10 text-violet-700 dark:border-violet-500/40 dark:bg-violet-500/15 dark:text-violet-300";
8
- readonly zinc: "border-zinc-300 bg-zinc-500/10 text-zinc-600 dark:border-zinc-600 dark:bg-zinc-500/15 dark:text-zinc-300";
1
+ export declare const BADGE_TONE: {
2
+ readonly blue: "border-blue-300 bg-blue-500/10 text-blue-700 dark:border-blue-500/40 dark:bg-blue-500/15 dark:text-blue-300";
3
+ readonly emerald: "border-emerald-300 bg-emerald-500/10 text-emerald-700 dark:border-emerald-500/40 dark:bg-emerald-500/15 dark:text-emerald-300";
4
+ readonly amber: "border-amber-300 bg-amber-500/10 text-amber-700 dark:border-amber-500/40 dark:bg-amber-500/15 dark:text-amber-300";
5
+ readonly red: "border-red-300 bg-red-500/10 text-red-700 dark:border-red-500/40 dark:bg-red-500/15 dark:text-red-300";
6
+ readonly violet: "border-violet-300 bg-violet-500/10 text-violet-700 dark:border-violet-500/40 dark:bg-violet-500/15 dark:text-violet-300";
7
+ readonly zinc: "border-zinc-300 bg-zinc-500/10 text-zinc-600 dark:border-zinc-600 dark:bg-zinc-500/15 dark:text-zinc-300";
9
8
  };
10
- type BadgeTone = keyof typeof BADGE_TONE;
9
+ export type BadgeTone = keyof typeof BADGE_TONE;
11
10
  /** Message and attachment lifecycle status to badge tone. */
12
- declare const STATUS_TONE: Record<string, BadgeTone>;
11
+ export declare const STATUS_TONE: Record<string, BadgeTone>;
13
12
  /** MCP connection state to badge tone. */
14
- declare const CONNECTION_TONE: Record<string, BadgeTone>;
15
- declare const BADGE_BASE = "inline-flex items-center rounded-md border px-1.5 py-0.5 text-[11px] font-medium leading-none";
16
- declare const BADGE_SM = "inline-flex items-center rounded border px-1 py-px text-[9px] font-medium leading-none";
13
+ export declare const CONNECTION_TONE: Record<string, BadgeTone>;
14
+ export declare const BADGE_BASE = "inline-flex items-center rounded-md border px-1.5 py-0.5 text-[11px] font-medium leading-none";
15
+ export declare const BADGE_SM = "inline-flex items-center rounded border px-1 py-px text-[9px] font-medium leading-none";
17
16
  /** Uniform width for short status labels in transcript rails (e.g. opt vs running). */
18
- declare const RAIL_STATUS_BADGE_CLASS = "min-w-[2.875rem] justify-center";
19
- //#endregion
20
- export { BADGE_BASE, BADGE_SM, BADGE_TONE, BadgeTone, CONNECTION_TONE, RAIL_STATUS_BADGE_CLASS, STATUS_TONE };
17
+ export declare const RAIL_STATUS_BADGE_CLASS = "min-w-[2.875rem] justify-center";
21
18
  //# sourceMappingURL=badgeTone.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"badgeTone.d.ts","names":[],"sources":["../../../src/views/ui/badgeTone.ts"],"mappings":";cAAa;;;;;;;;KAYD,yBAAyB;;cAGxB,aAAa,eAAe;;cAQ5B,iBAAiB,eAAe;cAShC;cAGA;;cAIA"}
1
+ {"version":3,"file":"badgeTone.d.ts","sourceRoot":"","sources":["../../../src/views/ui/badgeTone.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,UAAU;;;;;;;CAUb,CAAC;AAEX,MAAM,MAAM,SAAS,GAAG,MAAM,OAAO,UAAU,CAAC;AAEhD,6DAA6D;AAC7D,eAAO,MAAM,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,SAAS,CAKjD,CAAC;AAEF,0CAA0C;AAC1C,eAAO,MAAM,eAAe,EAAE,MAAM,CAAC,MAAM,EAAE,SAAS,CAOrD,CAAC;AAEF,eAAO,MAAM,UAAU,kGAC0E,CAAC;AAElG,eAAO,MAAM,QAAQ,2FACqE,CAAC;AAE3F,uFAAuF;AACvF,eAAO,MAAM,uBAAuB,oCAAoC,CAAC"}
@@ -1,5 +1,2 @@
1
- //#region src/views/ui/getInlineJson.d.ts
2
- declare function getInlineJson(value: unknown, limit: number): string | undefined;
3
- //#endregion
4
- export { getInlineJson };
1
+ export declare function getInlineJson(value: unknown, limit: number): string | undefined;
5
2
  //# sourceMappingURL=getInlineJson.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"getInlineJson.d.ts","names":[],"sources":["../../../src/views/ui/getInlineJson.ts"],"mappings":";iBAEgB,cAAc,gBAAgB"}
1
+ {"version":3,"file":"getInlineJson.d.ts","sourceRoot":"","sources":["../../../src/views/ui/getInlineJson.ts"],"names":[],"mappings":"AAEA,wBAAgB,aAAa,CAAC,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,sBAmC1D"}
@@ -1,16 +1,17 @@
1
- import { CenteredMessage } from "./CenteredMessage.js";
2
- import { Chip } from "./Chip.js";
3
- import { ControlButton } from "./ControlButton.js";
4
- import { CopyButton } from "./CopyButton.js";
5
- import { EmptyState } from "./EmptyState.js";
6
- import { InfoCard } from "./InfoCard.js";
7
- import { JSONTree } from "./JSONTree.js";
8
- import { PaneHeader, PaneHeaderStack } from "./PaneHeader.js";
9
- import { RailTime } from "./RailTime.js";
10
- import { SectionLabel } from "./SectionLabel.js";
11
- import { SelectableRow } from "./SelectableRow.js";
12
- import { Sparkline } from "./Sparkline.js";
13
- import { SummaryItem, SummaryList } from "./SummaryItem.js";
14
- import { BadgeTone, CONNECTION_TONE, RAIL_STATUS_BADGE_CLASS, STATUS_TONE } from "./badgeTone.js";
15
- import { ToneBadge } from "./ToneBadge.js";
16
- export { type BadgeTone, CONNECTION_TONE, CenteredMessage, Chip, ControlButton, CopyButton, EmptyState, InfoCard, JSONTree, PaneHeader, PaneHeaderStack, RAIL_STATUS_BADGE_CLASS, RailTime, STATUS_TONE, SectionLabel, SelectableRow, Sparkline, SummaryItem, SummaryList, ToneBadge };
1
+ export { CONNECTION_TONE, RAIL_STATUS_BADGE_CLASS, STATUS_TONE, } from "./badgeTone.js";
2
+ export type { BadgeTone } from "./badgeTone.js";
3
+ export { CenteredMessage } from "./CenteredMessage.js";
4
+ export { Chip } from "./Chip.js";
5
+ export { ControlButton } from "./ControlButton.js";
6
+ export { CopyButton } from "./CopyButton.js";
7
+ export { EmptyState } from "./EmptyState.js";
8
+ export { InfoCard } from "./InfoCard.js";
9
+ export { JSONTree } from "./JSONTree.js";
10
+ export { PaneHeader, PaneHeaderStack } from "./PaneHeader.js";
11
+ export { RailTime } from "./RailTime.js";
12
+ export { SectionLabel } from "./SectionLabel.js";
13
+ export { SelectableRow } from "./SelectableRow.js";
14
+ export { Sparkline } from "./Sparkline.js";
15
+ export { SummaryItem, SummaryList } from "./SummaryItem.js";
16
+ export { ToneBadge } from "./ToneBadge.js";
17
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/views/ui/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,eAAe,EACf,uBAAuB,EACvB,WAAW,GACZ,uBAAoB;AACrB,YAAY,EAAE,SAAS,EAAE,uBAAoB;AAC7C,OAAO,EAAE,eAAe,EAAE,6BAA0B;AACpD,OAAO,EAAE,IAAI,EAAE,kBAAe;AAC9B,OAAO,EAAE,aAAa,EAAE,2BAAwB;AAChD,OAAO,EAAE,UAAU,EAAE,wBAAqB;AAC1C,OAAO,EAAE,UAAU,EAAE,wBAAqB;AAC1C,OAAO,EAAE,QAAQ,EAAE,sBAAmB;AACtC,OAAO,EAAE,QAAQ,EAAE,sBAAmB;AACtC,OAAO,EAAE,UAAU,EAAE,eAAe,EAAE,wBAAqB;AAC3D,OAAO,EAAE,QAAQ,EAAE,sBAAmB;AACtC,OAAO,EAAE,YAAY,EAAE,0BAAuB;AAC9C,OAAO,EAAE,aAAa,EAAE,2BAAwB;AAChD,OAAO,EAAE,SAAS,EAAE,uBAAoB;AACxC,OAAO,EAAE,WAAW,EAAE,WAAW,EAAE,yBAAsB;AACzD,OAAO,EAAE,SAAS,EAAE,uBAAoB"}
@@ -1,10 +1,7 @@
1
- //#region src/views/ui/useCopyToClipboard.d.ts
2
- declare const useCopyToClipboard: ({ copiedDuration }?: {
3
- copiedDuration?: number;
1
+ export declare const useCopyToClipboard: ({ copiedDuration, }?: {
2
+ copiedDuration?: number;
4
3
  }) => {
5
- isCopied: boolean;
6
- copy: (value: string) => void;
4
+ isCopied: boolean;
5
+ copy: (value: string) => void;
7
6
  };
8
- //#endregion
9
- export { useCopyToClipboard };
10
7
  //# sourceMappingURL=useCopyToClipboard.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"useCopyToClipboard.d.ts","names":[],"sources":["../../../src/views/ui/useCopyToClipboard.ts"],"mappings":";cAEa,uBAAsB;EAGjC;;;EAKU,OAAA"}
1
+ {"version":3,"file":"useCopyToClipboard.d.ts","sourceRoot":"","sources":["../../../src/views/ui/useCopyToClipboard.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,kBAAkB,GAAI,sBAEhC;IACD,cAAc,CAAC,EAAE,MAAM,CAAC;CACpB;;kBAIM,MAAM;CAgBjB,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@assistant-ui/react-devtools",
3
- "version": "1.2.19",
3
+ "version": "1.2.21",
4
4
  "description": "React development tools for assistant-ui components",
5
5
  "keywords": [
6
6
  "assistant-ui",
@@ -28,11 +28,11 @@
28
28
  "sideEffects": false,
29
29
  "dependencies": {
30
30
  "clsx": "^2.1.1",
31
- "zod": "^4.5.4"
31
+ "zod": "^4.6.5"
32
32
  },
33
33
  "peerDependencies": {
34
34
  "@assistant-ui/react": "^0.15.0",
35
- "@assistant-ui/tap": "^0.9.17",
35
+ "@assistant-ui/tap": "^0.9.19",
36
36
  "@types/react": "*",
37
37
  "@types/react-dom": "*",
38
38
  "react": "^18 || ^19",
@@ -47,19 +47,19 @@
47
47
  }
48
48
  },
49
49
  "devDependencies": {
50
- "@assistant-ui/react": "0.15.20",
51
- "@assistant-ui/tap": "0.9.17",
52
- "@assistant-ui/x-buildutils": "0.0.27",
50
+ "@assistant-ui/react": "0.15.22",
51
+ "@assistant-ui/tap": "0.9.19",
52
+ "@assistant-ui/x-buildutils": "0.0.29",
53
53
  "@assistant-ui/x-performance": "0.0.0",
54
54
  "@tailwindcss/cli": "^4.3.3",
55
- "@types/node": "^26.4.1",
56
- "@types/react": "^19.2.18",
57
- "@types/react-dom": "^19.2.7",
55
+ "@types/node": "^26.5.1",
56
+ "@types/react": "^19.3.0",
57
+ "@types/react-dom": "^19.3.0",
58
58
  "jsdom": "^30.0.1",
59
- "react": "^19.2.8",
60
- "react-dom": "^19.2.8",
59
+ "react": "^19.3.0",
60
+ "react-dom": "^19.3.0",
61
61
  "tailwindcss": "^4.3.3",
62
- "vitest": "^5.0.0"
62
+ "vitest": "^5.0.1"
63
63
  },
64
64
  "publishConfig": {
65
65
  "access": "public",
@@ -77,6 +77,7 @@
77
77
  "scripts": {
78
78
  "build:css": "node scripts/build-styles.mjs",
79
79
  "build": "pnpm build:css && aui-build",
80
- "test": "vitest run"
80
+ "test": "vitest run",
81
+ "typecheck": "tsc --noEmit && tsc --noEmit -p tsconfig.test.json"
81
82
  }
82
83
  }
@@ -136,4 +136,45 @@ describe("projectApi", () => {
136
136
  const scopes = result.scopes as Array<{ name: string }>;
137
137
  expect(scopes.map((s) => s.name)).toEqual(["broken", "throwing"]);
138
138
  });
139
+
140
+ it("collects snapshots for prototype-named thread ids", () => {
141
+ const threads = scope(
142
+ "root",
143
+ {},
144
+ {
145
+ getState: () => ({
146
+ threadIds: ["__proto__"],
147
+ archivedThreadIds: [],
148
+ }),
149
+ __internal_getAssistantRuntime: () => ({
150
+ threads: {
151
+ getById: () => ({
152
+ getState: () => ({
153
+ messages: [],
154
+ suggestions: [],
155
+ capabilities: {},
156
+ }),
157
+ composer: { getState: () => ({ text: "" }) },
158
+ }),
159
+ },
160
+ }),
161
+ },
162
+ );
163
+
164
+ const projected = projectApi(1, {
165
+ api: { threads },
166
+ logs: [],
167
+ } as unknown as Parameters<typeof projectApi>[1]);
168
+
169
+ expect(Object.hasOwn(projected.threadSnapshots ?? {}, "__proto__")).toBe(
170
+ true,
171
+ );
172
+ expect(Object.getPrototypeOf(projected.threadSnapshots)).toBe(
173
+ Object.prototype,
174
+ );
175
+ expect(projected.threadSnapshots?.["__proto__"]).toMatchObject({
176
+ messages: [],
177
+ composer: { text: "" },
178
+ });
179
+ });
139
180
  });
@@ -82,7 +82,7 @@ const collectThreadSnapshots = (
82
82
  ];
83
83
  if (threadIds.length === 0) return undefined;
84
84
 
85
- const snapshots: Record<string, unknown> = {};
85
+ const snapshots: Record<string, unknown> = Object.create(null);
86
86
  for (const threadId of threadIds) {
87
87
  try {
88
88
  const threadRuntime = assistantRuntime.threads.getById(threadId);
@@ -101,7 +101,7 @@ const collectThreadSnapshots = (
101
101
  }
102
102
  }
103
103
 
104
- return Object.keys(snapshots).length > 0 ? snapshots : undefined;
104
+ return Object.keys(snapshots).length > 0 ? { ...snapshots } : undefined;
105
105
  };
106
106
 
107
107
  /**
@@ -307,3 +307,84 @@ describe("serializeModelContext", () => {
307
307
  });
308
308
  });
309
309
  });
310
+
311
+ describe("sanitizeForMessage errors", () => {
312
+ it("keeps the name, message and stack an Error hides behind non-enumerable properties", () => {
313
+ const result = sanitizeForMessage(new Error("boom")) as Record<
314
+ string,
315
+ unknown
316
+ >;
317
+
318
+ expect(result["name"]).toBe("Error");
319
+ expect(result["message"]).toBe("boom");
320
+ expect(typeof result["stack"]).toBe("string");
321
+ });
322
+
323
+ it("keeps custom fields and the error subclass name", () => {
324
+ class HttpError extends Error {
325
+ override name = "HttpError";
326
+ status = 503;
327
+ }
328
+
329
+ const result = sanitizeForMessage(new HttpError("unavailable")) as Record<
330
+ string,
331
+ unknown
332
+ >;
333
+
334
+ expect(result["name"]).toBe("HttpError");
335
+ expect(result["message"]).toBe("unavailable");
336
+ expect(result["status"]).toBe(503);
337
+ });
338
+
339
+ it("serializes a cause chain", () => {
340
+ const result = sanitizeForMessage(
341
+ new Error("outer", { cause: new Error("inner") }),
342
+ ) as Record<string, unknown>;
343
+
344
+ expect(result["cause"]).toMatchObject({ name: "Error", message: "inner" });
345
+ });
346
+
347
+ it("serializes an error nested in a value", () => {
348
+ const result = sanitizeForMessage({
349
+ status: { reason: "error", error: new Error("nested") },
350
+ }) as { status: { error: Record<string, unknown> } };
351
+
352
+ expect(result.status.error["message"]).toBe("nested");
353
+ });
354
+
355
+ it("sanitizes non-JSON values assigned to name, message or stack", () => {
356
+ const error = new Error("boom");
357
+ Object.defineProperty(error, "name", { value: Symbol("weird") });
358
+ Object.defineProperty(error, "message", { value: 10n });
359
+ Object.defineProperty(error, "stack", { value: { nested: 1n } });
360
+
361
+ const result = sanitizeForMessage(error) as Record<string, unknown>;
362
+
363
+ expect(result["name"]).toBe("Symbol(weird)");
364
+ expect(result["message"]).toBe("10");
365
+ expect(result["stack"]).toEqual({ nested: "1" });
366
+ expect(() => JSON.stringify(result)).not.toThrow();
367
+ });
368
+
369
+ it("serializes an error from another realm", () => {
370
+ // a cross-realm Error fails instanceof; both it and this stand-in answer
371
+ // the Object.prototype.toString brand check the same way
372
+ // a real cross-realm Error keeps name/message non-enumerable, so only the
373
+ // brand check can surface them; both answer Object.prototype.toString alike
374
+ const foreign = { [Symbol.toStringTag]: "Error" };
375
+ Object.defineProperty(foreign, "name", { value: "ForeignError" });
376
+ Object.defineProperty(foreign, "message", { value: "from an iframe" });
377
+
378
+ const result = sanitizeForMessage(foreign) as Record<string, unknown>;
379
+
380
+ expect(result["name"]).toBe("ForeignError");
381
+ expect(result["message"]).toBe("from an iframe");
382
+ });
383
+
384
+ it("does not recurse forever on a self-referencing cause", () => {
385
+ const error = new Error("loop") as Error & { cause?: unknown };
386
+ error.cause = error;
387
+
388
+ expect(() => sanitizeForMessage(error)).not.toThrow();
389
+ });
390
+ });
@@ -20,6 +20,12 @@ const setOwnProperty = (
20
20
  }
21
21
  };
22
22
 
23
+ // An Error from an iframe or worker fails `instanceof`, so the brand check is
24
+ // what keeps a cross-realm error from serializing as an empty object.
25
+ const isErrorLike = (value: object): boolean =>
26
+ value instanceof Error ||
27
+ Object.prototype.toString.call(value) === "[object Error]";
28
+
23
29
  export const sanitizeForMessage = (
24
30
  value: unknown,
25
31
  seen = new WeakSet<object>(),
@@ -99,6 +105,36 @@ export const sanitizeForMessage = (
99
105
  return result;
100
106
  }
101
107
 
108
+ // `name`, `message` and `stack` are not enumerable, so the branch below
109
+ // would render every Error as an empty object. Each is sanitized like any
110
+ // other value, since an Error carries whatever its author assigned.
111
+ if (isErrorLike(value)) {
112
+ const error: Record<string, unknown> = {
113
+ name: sanitizeForMessage(readProperty(value, "name"), seen),
114
+ message: sanitizeForMessage(readProperty(value, "message"), seen),
115
+ };
116
+ const stack = readProperty(value, "stack");
117
+ if (stack !== undefined) {
118
+ error["stack"] = sanitizeForMessage(stack, seen);
119
+ }
120
+ const cause = readProperty(value, "cause");
121
+ if (cause !== undefined) {
122
+ error["cause"] = sanitizeForMessage(cause, seen);
123
+ }
124
+ for (const key of Object.keys(value)) {
125
+ try {
126
+ setOwnProperty(
127
+ error,
128
+ key,
129
+ sanitizeForMessage(readProperty(value, key), seen),
130
+ );
131
+ } catch {
132
+ setOwnProperty(error, key, UNSERIALIZABLE);
133
+ }
134
+ }
135
+ return error;
136
+ }
137
+
102
138
  const result: Record<string, unknown> = {};
103
139
  for (const key of Object.keys(value)) {
104
140
  try {