@gram-ai/elements 1.27.4 → 1.27.6

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 (278) hide show
  1. package/README.md +72 -60
  2. package/README.typedoc.md +6 -6
  3. package/bin/cli.js +74 -74
  4. package/dist/compat-shims-CO9JXXV4.cjs.map +1 -1
  5. package/dist/{compat-shims-BPJ7Q68c.js → compat-shims-DxtUrORi.js} +4 -2
  6. package/dist/compat-shims-DxtUrORi.js.map +1 -0
  7. package/dist/components/ShareButton/index.d.ts +2 -2
  8. package/dist/components/assistant-ui/message-feedback.d.ts +1 -1
  9. package/dist/components/assistant-ui/tooltip-icon-button.d.ts +2 -2
  10. package/dist/components/ui/avatar.d.ts +2 -2
  11. package/dist/components/ui/button.d.ts +1 -1
  12. package/dist/components/ui/calendar.d.ts +1 -1
  13. package/dist/components/ui/collapsible.d.ts +1 -1
  14. package/dist/components/ui/dialog.d.ts +4 -4
  15. package/dist/components/ui/popover.d.ts +2 -2
  16. package/dist/components/ui/skeleton.d.ts +1 -1
  17. package/dist/components/ui/time-range-picker.d.ts +1 -1
  18. package/dist/components/ui/tool-ui.d.ts +7 -7
  19. package/dist/components/ui/tooltip.d.ts +2 -2
  20. package/dist/contexts/ConnectionStatusContext.d.ts +1 -1
  21. package/dist/elements.cjs +1 -1
  22. package/dist/elements.css +1 -1
  23. package/dist/elements.js +2 -2
  24. package/dist/hooks/useDensity.d.ts +73 -73
  25. package/dist/hooks/useMCPTools.d.ts +1 -1
  26. package/dist/hooks/useRadius.d.ts +1 -1
  27. package/dist/{index-KSX4Qjip.cjs → index-A17b62wR.cjs} +10 -10
  28. package/dist/index-A17b62wR.cjs.map +1 -0
  29. package/dist/{index-BpJstUh1.cjs → index-C4bFBGfl.cjs} +4 -4
  30. package/dist/{index-BpJstUh1.cjs.map → index-C4bFBGfl.cjs.map} +1 -1
  31. package/dist/{index-CUitXazZ.js → index-D93pV0_o.js} +55 -55
  32. package/dist/{index-CUitXazZ.js.map → index-D93pV0_o.js.map} +1 -1
  33. package/dist/{index-D0bAYNQy.js → index-Dm2wLFTN.js} +304 -282
  34. package/dist/index-Dm2wLFTN.js.map +1 -0
  35. package/dist/lib/cassette.d.ts +4 -4
  36. package/dist/lib/errorTracking.d.ts +1 -1
  37. package/dist/lib/messageConverter.d.ts +1 -1
  38. package/dist/lib/models.d.ts +1 -1
  39. package/dist/plugins/chart/ui/bar-chart.d.ts +1 -1
  40. package/dist/plugins/generative-ui/ui/accordion-wrapper.d.ts +2 -2
  41. package/dist/plugins/generative-ui/ui/accordion.d.ts +1 -1
  42. package/dist/plugins/generative-ui/ui/action-button.d.ts +2 -2
  43. package/dist/plugins/generative-ui/ui/alert-wrapper.d.ts +1 -1
  44. package/dist/plugins/generative-ui/ui/alert.d.ts +4 -4
  45. package/dist/plugins/generative-ui/ui/avatar.d.ts +5 -5
  46. package/dist/plugins/generative-ui/ui/badge.d.ts +2 -2
  47. package/dist/plugins/generative-ui/ui/button-wrapper.d.ts +2 -2
  48. package/dist/plugins/generative-ui/ui/button.d.ts +2 -2
  49. package/dist/plugins/generative-ui/ui/card-wrapper.d.ts +2 -2
  50. package/dist/plugins/generative-ui/ui/card.d.ts +8 -8
  51. package/dist/plugins/generative-ui/ui/checkbox.d.ts +1 -1
  52. package/dist/plugins/generative-ui/ui/data-table.d.ts +2 -2
  53. package/dist/plugins/generative-ui/ui/dialog.d.ts +3 -3
  54. package/dist/plugins/generative-ui/ui/dropdown-menu.d.ts +3 -3
  55. package/dist/plugins/generative-ui/ui/grid.d.ts +3 -3
  56. package/dist/plugins/generative-ui/ui/input-wrapper.d.ts +1 -1
  57. package/dist/plugins/generative-ui/ui/input.d.ts +2 -2
  58. package/dist/plugins/generative-ui/ui/label.d.ts +1 -1
  59. package/dist/plugins/generative-ui/ui/metric.d.ts +3 -3
  60. package/dist/plugins/generative-ui/ui/pagination.d.ts +6 -6
  61. package/dist/plugins/generative-ui/ui/popover.d.ts +4 -4
  62. package/dist/plugins/generative-ui/ui/progress.d.ts +2 -2
  63. package/dist/plugins/generative-ui/ui/radio-group.d.ts +1 -1
  64. package/dist/plugins/generative-ui/ui/select.d.ts +2 -2
  65. package/dist/plugins/generative-ui/ui/separator.d.ts +1 -1
  66. package/dist/plugins/generative-ui/ui/skeleton.d.ts +1 -1
  67. package/dist/plugins/generative-ui/ui/stack.d.ts +6 -6
  68. package/dist/plugins/generative-ui/ui/switch.d.ts +2 -2
  69. package/dist/plugins/generative-ui/ui/table.d.ts +9 -9
  70. package/dist/plugins/generative-ui/ui/tabs-wrapper.d.ts +1 -1
  71. package/dist/plugins/generative-ui/ui/tabs.d.ts +1 -1
  72. package/dist/plugins/generative-ui/ui/text.d.ts +3 -3
  73. package/dist/plugins/generative-ui/ui/textarea.d.ts +2 -2
  74. package/dist/plugins/generative-ui/ui/tooltip.d.ts +1 -1
  75. package/dist/plugins.cjs +1 -1
  76. package/dist/plugins.js +1 -1
  77. package/dist/{profiler-CyzxBxVz.cjs → profiler-Cbbf4eEX.cjs} +2 -2
  78. package/dist/{profiler-CyzxBxVz.cjs.map → profiler-Cbbf4eEX.cjs.map} +1 -1
  79. package/dist/{profiler-BFkhZRxj.js → profiler-mca4IXaY.js} +2 -2
  80. package/dist/{profiler-BFkhZRxj.js.map → profiler-mca4IXaY.js.map} +1 -1
  81. package/dist/react-shim.js +1 -1
  82. package/dist/server/express.cjs.map +1 -1
  83. package/dist/server/express.js.map +1 -1
  84. package/dist/{startRecording-C-PPAs_Z.js → startRecording-BCafdS7B.js} +2 -2
  85. package/dist/{startRecording-C-PPAs_Z.js.map → startRecording-BCafdS7B.js.map} +1 -1
  86. package/dist/{startRecording-Dq92sEHf.cjs → startRecording-Eb5f7wqP.cjs} +2 -2
  87. package/dist/{startRecording-Dq92sEHf.cjs.map → startRecording-Eb5f7wqP.cjs.map} +1 -1
  88. package/dist/types/index.d.ts +4 -4
  89. package/package.json +1 -5
  90. package/src/compat-plugin.ts +14 -14
  91. package/src/compat-shims.ts +33 -31
  92. package/src/compat.test.ts +48 -48
  93. package/src/compat.ts +6 -6
  94. package/src/components/Chat/index.tsx +17 -17
  95. package/src/components/Chat/stories/Charts.stories.tsx +98 -98
  96. package/src/components/Chat/stories/Composer.stories.tsx +15 -15
  97. package/src/components/Chat/stories/ConnectionConfiguration.stories.tsx +44 -44
  98. package/src/components/Chat/stories/CustomComponents.stories.tsx +17 -17
  99. package/src/components/Chat/stories/Density.stories.tsx +20 -20
  100. package/src/components/Chat/stories/ErrorBoundary.stories.tsx +47 -47
  101. package/src/components/Chat/stories/FrontendTools.stories.tsx +39 -39
  102. package/src/components/Chat/stories/GenerativeUI.stories.tsx +48 -48
  103. package/src/components/Chat/stories/MessageFeedback.stories.tsx +52 -52
  104. package/src/components/Chat/stories/Modal.stories.tsx +28 -28
  105. package/src/components/Chat/stories/Model.stories.tsx +11 -11
  106. package/src/components/Chat/stories/Radius.stories.tsx +20 -20
  107. package/src/components/Chat/stories/Sidecar.stories.tsx +13 -13
  108. package/src/components/Chat/stories/StyleIsolation.stories.tsx +11 -11
  109. package/src/components/Chat/stories/Theme.stories.tsx +25 -25
  110. package/src/components/Chat/stories/Thread.stories.tsx +25 -25
  111. package/src/components/Chat/stories/ToolApproval.stories.tsx +55 -55
  112. package/src/components/Chat/stories/ToolMentions.stories.tsx +17 -17
  113. package/src/components/Chat/stories/Tools.stories.tsx +88 -88
  114. package/src/components/Chat/stories/Variants.stories.tsx +32 -32
  115. package/src/components/Chat/stories/Welcome.stories.tsx +14 -14
  116. package/src/components/ChatHistory.tsx +7 -7
  117. package/src/components/FrontendTools/index.tsx +5 -5
  118. package/src/components/Replay.stories.tsx +157 -157
  119. package/src/components/Replay.tsx +76 -73
  120. package/src/components/ShadowRoot.tsx +40 -40
  121. package/src/components/ShareButton/index.tsx +32 -32
  122. package/src/components/assistant-ui/assistant-modal.tsx +92 -87
  123. package/src/components/assistant-ui/assistant-sidecar.tsx +35 -35
  124. package/src/components/assistant-ui/attachment.tsx +80 -80
  125. package/src/components/assistant-ui/connection-status-indicator.tsx +33 -33
  126. package/src/components/assistant-ui/error-boundary.tsx +34 -34
  127. package/src/components/assistant-ui/follow-on-suggestions.tsx +26 -26
  128. package/src/components/assistant-ui/markdown-text.tsx +69 -69
  129. package/src/components/assistant-ui/mentioned-tools-badges.tsx +38 -38
  130. package/src/components/assistant-ui/message-feedback.tsx +57 -50
  131. package/src/components/assistant-ui/reasoning.tsx +83 -83
  132. package/src/components/assistant-ui/thread-list.tsx +45 -45
  133. package/src/components/assistant-ui/thread.tsx +278 -278
  134. package/src/components/assistant-ui/tool-fallback.tsx +37 -37
  135. package/src/components/assistant-ui/tool-group.tsx +26 -26
  136. package/src/components/assistant-ui/tool-mention-autocomplete.tsx +122 -122
  137. package/src/components/assistant-ui/tooltip-icon-button.tsx +18 -18
  138. package/src/components/ui/avatar.tsx +12 -12
  139. package/src/components/ui/button.tsx +12 -12
  140. package/src/components/ui/buttonVariants.ts +17 -17
  141. package/src/components/ui/calendar.tsx +106 -106
  142. package/src/components/ui/charts.stories.tsx +56 -56
  143. package/src/components/ui/collapsible.tsx +5 -5
  144. package/src/components/ui/dialog.tsx +30 -30
  145. package/src/components/ui/generative-ui.stories.tsx +200 -200
  146. package/src/components/ui/generative-ui.tsx +26 -26
  147. package/src/components/ui/popover.tsx +14 -14
  148. package/src/components/ui/skeleton.tsx +5 -5
  149. package/src/components/ui/time-range-picker.stories.tsx +80 -80
  150. package/src/components/ui/time-range-picker.tsx +248 -246
  151. package/src/components/ui/tool-ui.stories.tsx +37 -37
  152. package/src/components/ui/tool-ui.tsx +221 -215
  153. package/src/components/ui/tooltip.tsx +15 -15
  154. package/src/constants/tailwind.ts +1 -1
  155. package/src/contexts/ChatIdContext.tsx +7 -7
  156. package/src/contexts/ConnectionStatusContext.tsx +64 -64
  157. package/src/contexts/ElementsProvider.tsx +214 -213
  158. package/src/contexts/ReplayContext.ts +3 -3
  159. package/src/contexts/ToolApprovalContext.tsx +54 -54
  160. package/src/contexts/ToolExecutionContext.tsx +34 -34
  161. package/src/contexts/contexts.ts +7 -7
  162. package/src/contexts/portal-container-context.ts +2 -2
  163. package/src/contexts/portal-container.tsx +7 -7
  164. package/src/embedded.ts +1 -1
  165. package/src/global.css +25 -25
  166. package/src/hooks/useAuth.ts +72 -72
  167. package/src/hooks/useDensity.ts +79 -79
  168. package/src/hooks/useElements.ts +6 -6
  169. package/src/hooks/useExpanded.ts +12 -12
  170. package/src/hooks/useFollowOnSuggestions.ts +83 -83
  171. package/src/hooks/useGramThreadListAdapter.tsx +99 -99
  172. package/src/hooks/useMCPTools.ts +47 -47
  173. package/src/hooks/useModel.ts +14 -14
  174. package/src/hooks/usePluginComponents.ts +11 -11
  175. package/src/hooks/usePortalContainer.ts +5 -5
  176. package/src/hooks/useRadius.ts +23 -23
  177. package/src/hooks/useRecordCassette.ts +34 -34
  178. package/src/hooks/useSession.ts +11 -11
  179. package/src/hooks/useThemeProps.ts +13 -13
  180. package/src/hooks/useThreadId.ts +4 -4
  181. package/src/hooks/useToolApproval.ts +7 -7
  182. package/src/hooks/useToolMentions.ts +40 -40
  183. package/src/index.ts +26 -26
  184. package/src/lib/api.test.ts +61 -61
  185. package/src/lib/api.ts +4 -3
  186. package/src/lib/auth.ts +13 -13
  187. package/src/lib/cassette.ts +84 -84
  188. package/src/lib/easing.ts +1 -1
  189. package/src/lib/errorTracking.config.ts +5 -5
  190. package/src/lib/errorTracking.ts +29 -29
  191. package/src/lib/generative-ui.ts +7 -7
  192. package/src/lib/humanize.ts +3 -3
  193. package/src/lib/messageConverter.test.ts +130 -127
  194. package/src/lib/messageConverter.ts +196 -196
  195. package/src/lib/models.ts +28 -20
  196. package/src/lib/token.test.ts +56 -56
  197. package/src/lib/token.ts +14 -14
  198. package/src/lib/tool-mentions.ts +45 -45
  199. package/src/lib/tools.ts +66 -62
  200. package/src/lib/utils.ts +5 -5
  201. package/src/lib.d.ts +1 -1
  202. package/src/plugins/README.md +5 -5
  203. package/src/plugins/chart/catalog.ts +18 -18
  204. package/src/plugins/chart/chart.test.ts +31 -31
  205. package/src/plugins/chart/component.tsx +34 -34
  206. package/src/plugins/chart/index.ts +4 -4
  207. package/src/plugins/chart/ui/area-chart.tsx +42 -42
  208. package/src/plugins/chart/ui/bar-chart.tsx +46 -46
  209. package/src/plugins/chart/ui/donut-chart.tsx +48 -48
  210. package/src/plugins/chart/ui/index.ts +7 -7
  211. package/src/plugins/chart/ui/line-chart.tsx +43 -43
  212. package/src/plugins/chart/ui/pie-chart.tsx +44 -44
  213. package/src/plugins/chart/ui/radar-chart.tsx +33 -33
  214. package/src/plugins/chart/ui/scatter-chart.tsx +43 -43
  215. package/src/plugins/components/MacOSWindowFrame.tsx +15 -15
  216. package/src/plugins/components/PluginLoadingState.tsx +10 -10
  217. package/src/plugins/components/index.ts +1 -1
  218. package/src/plugins/generative-ui/catalog.ts +54 -54
  219. package/src/plugins/generative-ui/component.tsx +85 -85
  220. package/src/plugins/generative-ui/index.ts +4 -4
  221. package/src/plugins/generative-ui/ui/accordion-wrapper.tsx +16 -16
  222. package/src/plugins/generative-ui/ui/accordion.tsx +16 -16
  223. package/src/plugins/generative-ui/ui/action-button.tsx +28 -28
  224. package/src/plugins/generative-ui/ui/alert-wrapper.tsx +8 -8
  225. package/src/plugins/generative-ui/ui/alert.tsx +20 -20
  226. package/src/plugins/generative-ui/ui/avatar-wrapper.tsx +7 -7
  227. package/src/plugins/generative-ui/ui/avatar.tsx +30 -30
  228. package/src/plugins/generative-ui/ui/badge.tsx +22 -22
  229. package/src/plugins/generative-ui/ui/button-wrapper.tsx +12 -12
  230. package/src/plugins/generative-ui/ui/button.tsx +28 -28
  231. package/src/plugins/generative-ui/ui/card-wrapper.tsx +8 -8
  232. package/src/plugins/generative-ui/ui/card.tsx +27 -27
  233. package/src/plugins/generative-ui/ui/checkbox-wrapper.tsx +9 -9
  234. package/src/plugins/generative-ui/ui/checkbox.tsx +9 -9
  235. package/src/plugins/generative-ui/ui/data-table.tsx +8 -8
  236. package/src/plugins/generative-ui/ui/dialog.tsx +31 -31
  237. package/src/plugins/generative-ui/ui/dropdown-menu.tsx +44 -44
  238. package/src/plugins/generative-ui/ui/grid.tsx +12 -12
  239. package/src/plugins/generative-ui/ui/index.ts +40 -40
  240. package/src/plugins/generative-ui/ui/input-wrapper.tsx +11 -11
  241. package/src/plugins/generative-ui/ui/input.tsx +9 -9
  242. package/src/plugins/generative-ui/ui/label.tsx +8 -8
  243. package/src/plugins/generative-ui/ui/list.tsx +11 -11
  244. package/src/plugins/generative-ui/ui/metric.tsx +23 -23
  245. package/src/plugins/generative-ui/ui/pagination.tsx +28 -28
  246. package/src/plugins/generative-ui/ui/popover.tsx +21 -21
  247. package/src/plugins/generative-ui/ui/progress.tsx +13 -13
  248. package/src/plugins/generative-ui/ui/radio-group.tsx +12 -12
  249. package/src/plugins/generative-ui/ui/select-wrapper.tsx +7 -7
  250. package/src/plugins/generative-ui/ui/select.tsx +37 -37
  251. package/src/plugins/generative-ui/ui/separator.tsx +9 -9
  252. package/src/plugins/generative-ui/ui/skeleton-wrapper.tsx +10 -10
  253. package/src/plugins/generative-ui/ui/skeleton.tsx +5 -5
  254. package/src/plugins/generative-ui/ui/stack.tsx +28 -28
  255. package/src/plugins/generative-ui/ui/switch.tsx +11 -11
  256. package/src/plugins/generative-ui/ui/table.tsx +32 -32
  257. package/src/plugins/generative-ui/ui/tabs-wrapper.tsx +11 -11
  258. package/src/plugins/generative-ui/ui/tabs.tsx +26 -26
  259. package/src/plugins/generative-ui/ui/text.tsx +12 -12
  260. package/src/plugins/generative-ui/ui/textarea.tsx +7 -7
  261. package/src/plugins/generative-ui/ui/tooltip.tsx +12 -12
  262. package/src/plugins/index.ts +7 -7
  263. package/src/react-shim.ts +6 -6
  264. package/src/server/bun.ts +12 -12
  265. package/src/server/core.ts +25 -25
  266. package/src/server/express.ts +17 -15
  267. package/src/server/fastify.ts +14 -14
  268. package/src/server/hono.ts +9 -9
  269. package/src/server/nextjs.ts +12 -12
  270. package/src/server/tanstack-start.ts +12 -12
  271. package/src/server.ts +27 -27
  272. package/src/storybook.d.ts +4 -4
  273. package/src/types/index.ts +124 -124
  274. package/src/types/plugins.ts +7 -7
  275. package/src/vite-env.d.ts +12 -12
  276. package/dist/compat-shims-BPJ7Q68c.js.map +0 -1
  277. package/dist/index-D0bAYNQy.js.map +0 -1
  278. package/dist/index-KSX4Qjip.cjs.map +0 -1
@@ -1,6 +1,6 @@
1
- import * as React from 'react'
2
- import { useState, useEffect } from 'react'
3
- import { cva } from 'class-variance-authority'
1
+ import * as React from "react";
2
+ import { useState, useEffect } from "react";
3
+ import { cva } from "class-variance-authority";
4
4
  import {
5
5
  CheckIcon,
6
6
  ChevronDownIcon,
@@ -9,94 +9,98 @@ import {
9
9
  CopyIcon,
10
10
  LoaderIcon,
11
11
  XIcon,
12
- } from 'lucide-react'
13
- import { cn } from '@/lib/utils'
14
- import { codeToHtml, BundledLanguage } from 'shiki'
15
- import { Button } from './button'
16
- import { Popover, PopoverAnchor, PopoverContent } from './popover'
12
+ } from "lucide-react";
13
+ import { cn } from "@/lib/utils";
14
+ import { codeToHtml, BundledLanguage } from "shiki";
15
+ import { Button } from "./button";
16
+ import { Popover, PopoverAnchor, PopoverContent } from "./popover";
17
17
 
18
18
  /* -----------------------------------------------------------------------------
19
19
  * Status indicator styles
20
20
  * -------------------------------------------------------------------------- */
21
21
 
22
22
  const statusVariants = cva(
23
- 'flex size-5 items-center justify-center rounded-full',
23
+ "flex size-5 items-center justify-center rounded-full",
24
24
  {
25
25
  variants: {
26
26
  status: {
27
- pending: 'border border-dashed border-muted-foreground/50',
28
- running: 'text-primary',
29
- complete: 'text-green-600 dark:text-green-500',
30
- error: 'text-destructive',
31
- approval: 'text-amber-500',
27
+ pending: "border border-dashed border-muted-foreground/50",
28
+ running: "text-primary",
29
+ complete: "text-green-600 dark:text-green-500",
30
+ error: "text-destructive",
31
+ approval: "text-amber-500",
32
32
  },
33
33
  },
34
34
  defaultVariants: {
35
- status: 'pending',
35
+ status: "pending",
36
36
  },
37
- }
38
- )
37
+ },
38
+ );
39
39
 
40
40
  /* -----------------------------------------------------------------------------
41
41
  * Types
42
42
  * -------------------------------------------------------------------------- */
43
43
 
44
- type ToolStatus = 'pending' | 'running' | 'complete' | 'error' | 'approval'
44
+ type ToolStatus = "pending" | "running" | "complete" | "error" | "approval";
45
45
 
46
46
  type ContentItem =
47
- | { type: 'text'; text: string; _meta?: { 'getgram.ai/mime-type'?: string } }
48
- | { type: 'image'; data: string; _meta?: { 'getgram.ai/mime-type'?: string } }
47
+ | { type: "text"; text: string; _meta?: { "getgram.ai/mime-type"?: string } }
48
+ | {
49
+ type: "image";
50
+ data: string;
51
+ _meta?: { "getgram.ai/mime-type"?: string };
52
+ };
49
53
 
50
54
  /** MCP tool annotations providing hints about tool behavior */
51
55
  interface ToolAnnotations {
52
56
  /** Human-readable display name for the tool */
53
- title?: string
57
+ title?: string;
54
58
  /** If true, the tool does not modify its environment */
55
- readOnlyHint?: boolean
59
+ readOnlyHint?: boolean;
56
60
  /** If true, the tool may perform destructive updates */
57
- destructiveHint?: boolean
61
+ destructiveHint?: boolean;
58
62
  /** If true, repeated calls with same args have no additional effect */
59
- idempotentHint?: boolean
63
+ idempotentHint?: boolean;
60
64
  /** If true, tool interacts with external entities */
61
- openWorldHint?: boolean
65
+ openWorldHint?: boolean;
62
66
  }
63
67
 
64
68
  interface ToolUIProps {
65
69
  /** Display name of the tool */
66
- name: string
70
+ name: string;
67
71
  /** Optional icon to display (defaults to first letter of name) */
68
- icon?: React.ReactNode
72
+ icon?: React.ReactNode;
69
73
  /** Provider/source name (e.g., "Notion", "GitHub") */
70
- provider?: string
74
+ provider?: string;
71
75
  /** Current status of the tool execution */
72
- status?: ToolStatus
76
+ status?: ToolStatus;
73
77
  /** Request/input data - can be string or object */
74
- request?: string | Record<string, unknown>
78
+ request?: string | Record<string, unknown>;
75
79
  /** Result/output data - can be string, object, or structured content array */
76
- result?: string | Record<string, unknown> | { content: ContentItem[] }
80
+ result?: string | Record<string, unknown> | { content: ContentItem[] };
77
81
  /** Whether the tool card starts expanded */
78
- defaultExpanded?: boolean
82
+ defaultExpanded?: boolean;
79
83
  /** Additional class names */
80
- className?: string
84
+ className?: string;
81
85
  /** MCP tool annotations */
82
- annotations?: ToolAnnotations
86
+ annotations?: ToolAnnotations;
83
87
  /** Approval callbacks */
84
- onApproveOnce?: () => void
85
- onApproveForSession?: () => void
86
- onDeny?: () => void
88
+ onApproveOnce?: () => void;
89
+ onApproveForSession?: () => void;
90
+ onDeny?: () => void;
87
91
  }
88
92
 
89
93
  interface ToolUISectionProps {
90
94
  /** Section title */
91
- title: string
95
+ title: string;
92
96
  /** Content to display - string or object (will be JSON stringified) */
93
- content: string | Record<string, unknown> | { content: ContentItem[] }
97
+ content: string | Record<string, unknown> | { content: ContentItem[] };
94
98
  /** Whether section starts expanded */
95
- defaultExpanded?: boolean
99
+ defaultExpanded?: boolean;
96
100
  /** Enable syntax highlighting */
97
- highlightSyntax?: boolean
101
+ highlightSyntax?: boolean;
98
102
  /** Language hint for syntax highlighting */
99
- language?: BundledLanguage
103
+ language?: BundledLanguage;
100
104
  }
101
105
 
102
106
  /* -----------------------------------------------------------------------------
@@ -104,51 +108,51 @@ interface ToolUISectionProps {
104
108
  * -------------------------------------------------------------------------- */
105
109
 
106
110
  function getLanguageFromMimeType(
107
- mimeType: string
111
+ mimeType: string,
108
112
  ): BundledLanguage | undefined {
109
113
  switch (mimeType) {
110
- case 'text/markdown':
111
- return 'markdown'
112
- case 'text/html':
113
- return 'html'
114
- case 'text/css':
115
- return 'css'
116
- case 'application/json':
117
- return 'json'
118
- case 'text/javascript':
119
- return 'javascript'
120
- case 'text/typescript':
121
- return 'typescript'
122
- case 'text/python':
123
- return 'python'
114
+ case "text/markdown":
115
+ return "markdown";
116
+ case "text/html":
117
+ return "html";
118
+ case "text/css":
119
+ return "css";
120
+ case "application/json":
121
+ return "json";
122
+ case "text/javascript":
123
+ return "javascript";
124
+ case "text/typescript":
125
+ return "typescript";
126
+ case "text/python":
127
+ return "python";
124
128
  default:
125
- return undefined
129
+ return undefined;
126
130
  }
127
131
  }
128
132
 
129
133
  function formatTextForLanguage(
130
134
  text: string,
131
- language: BundledLanguage | undefined
135
+ language: BundledLanguage | undefined,
132
136
  ): string {
133
- if (language === 'json') {
137
+ if (language === "json") {
134
138
  try {
135
- return JSON.stringify(JSON.parse(text), null, 2)
139
+ return JSON.stringify(JSON.parse(text), null, 2);
136
140
  } catch {
137
- return text
141
+ return text;
138
142
  }
139
143
  }
140
- return text
144
+ return text;
141
145
  }
142
146
 
143
147
  function isStructuredContent(
144
- content: unknown
148
+ content: unknown,
145
149
  ): content is { content: ContentItem[] } {
146
150
  return (
147
- typeof content === 'object' &&
151
+ typeof content === "object" &&
148
152
  content !== null &&
149
- 'content' in content &&
153
+ "content" in content &&
150
154
  Array.isArray((content as { content: unknown }).content)
151
- )
155
+ );
152
156
  }
153
157
 
154
158
  /* -----------------------------------------------------------------------------
@@ -158,31 +162,31 @@ function isStructuredContent(
158
162
  function StatusIndicator({ status }: { status: ToolStatus }) {
159
163
  return (
160
164
  <div className={cn(statusVariants({ status }))}>
161
- {status === 'pending' && null}
162
- {status === 'running' && <LoaderIcon className="size-4 animate-spin" />}
163
- {status === 'complete' && <CheckIcon className="size-4" />}
164
- {status === 'error' && <XIcon className="size-4" />}
165
- {status === 'approval' && (
166
- <LoaderIcon className="text-muted-foreground size-4 animate-spin" />
165
+ {status === "pending" && null}
166
+ {status === "running" && <LoaderIcon className="size-4 animate-spin" />}
167
+ {status === "complete" && <CheckIcon className="size-4" />}
168
+ {status === "error" && <XIcon className="size-4" />}
169
+ {status === "approval" && (
170
+ <LoaderIcon className="size-4 animate-spin text-muted-foreground" />
167
171
  )}
168
172
  </div>
169
- )
173
+ );
170
174
  }
171
175
 
172
176
  function CopyButton({ content }: { content: string }) {
173
- const [copied, setCopied] = useState(false)
177
+ const [copied, setCopied] = useState(false);
174
178
 
175
179
  const handleCopy = async (e: React.MouseEvent) => {
176
- e.stopPropagation()
177
- await navigator.clipboard.writeText(content)
178
- setCopied(true)
179
- setTimeout(() => setCopied(false), 2000)
180
- }
180
+ e.stopPropagation();
181
+ await navigator.clipboard.writeText(content);
182
+ setCopied(true);
183
+ setTimeout(() => setCopied(false), 2000);
184
+ };
181
185
 
182
186
  return (
183
187
  <button
184
188
  onClick={handleCopy}
185
- className="text-muted-foreground hover:bg-accent hover:text-foreground rounded p-1 transition-colors"
189
+ className="rounded p-1 text-muted-foreground transition-colors hover:bg-accent hover:text-foreground"
186
190
  aria-label="Copy to clipboard"
187
191
  >
188
192
  {copied ? (
@@ -191,7 +195,7 @@ function CopyButton({ content }: { content: string }) {
191
195
  <CopyIcon className="size-4" />
192
196
  )}
193
197
  </button>
194
- )
198
+ );
195
199
  }
196
200
 
197
201
  /* -----------------------------------------------------------------------------
@@ -199,19 +203,19 @@ function CopyButton({ content }: { content: string }) {
199
203
  * -------------------------------------------------------------------------- */
200
204
 
201
205
  /** Max characters to send through shiki — above this we skip highlighting. */
202
- const SHIKI_CHAR_LIMIT = 8_000
206
+ const SHIKI_CHAR_LIMIT = 8_000;
203
207
  /** Max lines shown in the collapsed preview. */
204
- const PREVIEW_LINE_LIMIT = 50
208
+ const PREVIEW_LINE_LIMIT = 50;
205
209
 
206
210
  function truncateToLines(text: string, maxLines: number) {
207
- let pos = 0
211
+ let pos = 0;
208
212
  for (let i = 0; i < maxLines; i++) {
209
- const next = text.indexOf('\n', pos)
210
- if (next === -1) return { text, truncated: false, totalLines: i + 1 }
211
- pos = next + 1
213
+ const next = text.indexOf("\n", pos);
214
+ if (next === -1) return { text, truncated: false, totalLines: i + 1 };
215
+ pos = next + 1;
212
216
  }
213
- const totalLines = text.split('\n').length
214
- return { text: text.slice(0, pos), truncated: true, totalLines }
217
+ const totalLines = text.split("\n").length;
218
+ return { text: text.slice(0, pos), truncated: true, totalLines };
215
219
  }
216
220
 
217
221
  function SyntaxHighlightedCode({
@@ -219,43 +223,43 @@ function SyntaxHighlightedCode({
219
223
  language,
220
224
  className,
221
225
  }: {
222
- text: string
223
- language?: BundledLanguage
224
- className?: string
226
+ text: string;
227
+ language?: BundledLanguage;
228
+ className?: string;
225
229
  }) {
226
- const [highlightedCode, setHighlightedCode] = useState<string | null>(null)
227
- const [expanded, setExpanded] = useState(false)
230
+ const [highlightedCode, setHighlightedCode] = useState<string | null>(null);
231
+ const [expanded, setExpanded] = useState(false);
228
232
 
229
233
  const preview = React.useMemo(
230
234
  () => truncateToLines(text, PREVIEW_LINE_LIMIT),
231
- [text]
232
- )
233
- const displayText = expanded ? text : preview.text
234
- const canHighlight = displayText.length <= SHIKI_CHAR_LIMIT
235
+ [text],
236
+ );
237
+ const displayText = expanded ? text : preview.text;
238
+ const canHighlight = displayText.length <= SHIKI_CHAR_LIMIT;
235
239
 
236
240
  useEffect(() => {
237
- setHighlightedCode(null)
238
- if (!language || !canHighlight) return
239
- let cancelled = false
241
+ setHighlightedCode(null);
242
+ if (!language || !canHighlight) return;
243
+ let cancelled = false;
240
244
  codeToHtml(displayText, {
241
245
  lang: language,
242
- theme: 'github-dark-default',
243
- rootStyle: 'background-color: transparent;',
246
+ theme: "github-dark-default",
247
+ rootStyle: "background-color: transparent;",
244
248
  transformers: [
245
249
  {
246
250
  pre(node) {
247
251
  node.properties.class =
248
- 'w-full py-3 px-4 max-h-[300px] overflow-y-auto whitespace-pre-wrap text-left text-sm'
252
+ "w-full py-3 px-4 max-h-[300px] overflow-y-auto whitespace-pre-wrap text-left text-sm";
249
253
  },
250
254
  },
251
255
  ],
252
256
  }).then((html) => {
253
- if (!cancelled) setHighlightedCode(html)
254
- })
257
+ if (!cancelled) setHighlightedCode(html);
258
+ });
255
259
  return () => {
256
- cancelled = true
257
- }
258
- }, [displayText, language, canHighlight])
260
+ cancelled = true;
261
+ };
262
+ }, [displayText, language, canHighlight]);
259
263
 
260
264
  const showMoreButton = preview.truncated && !expanded && (
261
265
  <button
@@ -265,28 +269,28 @@ function SyntaxHighlightedCode({
265
269
  >
266
270
  Show all {preview.totalLines} lines…
267
271
  </button>
268
- )
272
+ );
269
273
 
270
274
  if (!canHighlight || !highlightedCode) {
271
275
  return (
272
- <div className={cn('w-full', className)}>
276
+ <div className={cn("w-full", className)}>
273
277
  <pre className="max-h-[300px] w-full overflow-y-auto bg-slate-800/90 px-4 py-3 text-sm whitespace-pre-wrap text-slate-100">
274
278
  {displayText}
275
279
  </pre>
276
280
  {showMoreButton}
277
281
  </div>
278
- )
282
+ );
279
283
  }
280
284
 
281
285
  return (
282
- <div className={cn('w-full', className)}>
286
+ <div className={cn("w-full", className)}>
283
287
  <div
284
288
  className="w-full bg-slate-800/90"
285
289
  dangerouslySetInnerHTML={{ __html: highlightedCode }}
286
290
  />
287
291
  {showMoreButton}
288
292
  </div>
289
- )
293
+ );
290
294
  }
291
295
 
292
296
  /* -----------------------------------------------------------------------------
@@ -294,7 +298,7 @@ function SyntaxHighlightedCode({
294
298
  * -------------------------------------------------------------------------- */
295
299
 
296
300
  function ImageContent({ data }: { data: string }) {
297
- const image = `data:image/png;base64,${data}`
301
+ const image = `data:image/png;base64,${data}`;
298
302
  return (
299
303
  <div
300
304
  className="flex items-center justify-center rounded-lg p-5"
@@ -303,13 +307,13 @@ function ImageContent({ data }: { data: string }) {
303
307
  linear-gradient(135deg, #ccc 25%, transparent 25%),
304
308
  linear-gradient(45deg, transparent 75%, #ccc 75%),
305
309
  linear-gradient(135deg, transparent 75%, #ccc 75%)`,
306
- backgroundSize: '25px 25px',
307
- backgroundPosition: '0 0, 12.5px 0, 12.5px -12.5px, 0px 12.5px',
310
+ backgroundSize: "25px 25px",
311
+ backgroundPosition: "0 0, 12.5px 0, 12.5px -12.5px, 0px 12.5px",
308
312
  }}
309
313
  >
310
314
  <img src={image} className="max-h-[300px] max-w-full object-contain" />
311
315
  </div>
312
- )
316
+ );
313
317
  }
314
318
 
315
319
  /* -----------------------------------------------------------------------------
@@ -319,27 +323,27 @@ function ImageContent({ data }: { data: string }) {
319
323
  function StructuredResultContent({
320
324
  content,
321
325
  }: {
322
- content: { content: ContentItem[] }
326
+ content: { content: ContentItem[] };
323
327
  }) {
324
328
  return (
325
329
  <div className="w-full">
326
330
  {content.content.map((item, index) => {
327
331
  switch (item.type) {
328
- case 'text': {
332
+ case "text": {
329
333
  const language = getLanguageFromMimeType(
330
- item._meta?.['getgram.ai/mime-type'] ?? 'text/plain'
331
- )
332
- const formattedText = formatTextForLanguage(item.text, language)
334
+ item._meta?.["getgram.ai/mime-type"] ?? "text/plain",
335
+ );
336
+ const formattedText = formatTextForLanguage(item.text, language);
333
337
  return (
334
338
  <SyntaxHighlightedCode
335
339
  key={index}
336
340
  text={formattedText}
337
341
  language={language}
338
342
  />
339
- )
343
+ );
340
344
  }
341
- case 'image': {
342
- return <ImageContent key={index} data={item.data} />
345
+ case "image": {
346
+ return <ImageContent key={index} data={item.data} />;
343
347
  }
344
348
  default:
345
349
  return (
@@ -349,11 +353,11 @@ function StructuredResultContent({
349
353
  >
350
354
  {JSON.stringify(item, null, 2)}
351
355
  </pre>
352
- )
356
+ );
353
357
  }
354
358
  })}
355
359
  </div>
356
- )
360
+ );
357
361
  }
358
362
 
359
363
  /* -----------------------------------------------------------------------------
@@ -365,53 +369,53 @@ function ToolUISection({
365
369
  content,
366
370
  defaultExpanded = false,
367
371
  highlightSyntax = true,
368
- language = 'json',
372
+ language = "json",
369
373
  }: ToolUISectionProps) {
370
- const [isExpanded, setIsExpanded] = useState(defaultExpanded)
374
+ const [isExpanded, setIsExpanded] = useState(defaultExpanded);
371
375
 
372
376
  // For structured content, we don't stringify it
373
- const isStructured = isStructuredContent(content)
377
+ const isStructured = isStructuredContent(content);
374
378
  const contentString = isStructured
375
379
  ? JSON.stringify(content, null, 2)
376
- : typeof content === 'string'
380
+ : typeof content === "string"
377
381
  ? content
378
- : JSON.stringify(content, null, 2)
382
+ : JSON.stringify(content, null, 2);
379
383
 
380
384
  return (
381
- <div data-slot="tool-ui-section" className="border-border border-t">
385
+ <div data-slot="tool-ui-section" className="border-t border-border">
382
386
  <button
383
387
  onClick={() => setIsExpanded(!isExpanded)}
384
- className="hover:bg-accent/50 flex w-full cursor-pointer items-center justify-between px-5 py-2.5 text-left transition-colors"
388
+ className="flex w-full cursor-pointer items-center justify-between px-5 py-2.5 text-left transition-colors hover:bg-accent/50"
385
389
  >
386
- <span className="text-muted-foreground text-sm">{title}</span>
390
+ <span className="text-sm text-muted-foreground">{title}</span>
387
391
  <div className="flex items-center gap-1">
388
392
  <CopyButton content={contentString} />
389
393
  <ChevronRightIcon
390
394
  className={cn(
391
- 'text-muted-foreground size-4 transition-transform duration-200',
392
- isExpanded && 'rotate-90'
395
+ "size-4 text-muted-foreground transition-transform duration-200",
396
+ isExpanded && "rotate-90",
393
397
  )}
394
398
  />
395
399
  </div>
396
400
  </button>
397
401
  {isExpanded && (
398
- <div className="border-border border-t">
402
+ <div className="border-t border-border">
399
403
  {isStructured ? (
400
404
  <StructuredResultContent content={content} />
401
405
  ) : highlightSyntax ? (
402
406
  <SyntaxHighlightedCode text={contentString} language={language} />
403
407
  ) : (
404
- <pre className="text-foreground overflow-x-auto px-4 py-3 text-sm whitespace-pre-wrap">
408
+ <pre className="overflow-x-auto px-4 py-3 text-sm whitespace-pre-wrap text-foreground">
405
409
  {contentString}
406
410
  </pre>
407
411
  )}
408
412
  </div>
409
413
  )}
410
414
  </div>
411
- )
415
+ );
412
416
  }
413
417
 
414
- type ApprovalMode = 'one-time' | 'for-session'
418
+ type ApprovalMode = "one-time" | "for-session";
415
419
 
416
420
  /* -----------------------------------------------------------------------------
417
421
  * ToolUI - Main component
@@ -421,7 +425,7 @@ function ToolUI({
421
425
  name,
422
426
  icon,
423
427
  provider,
424
- status = 'complete',
428
+ status = "complete",
425
429
  request,
426
430
  result,
427
431
  defaultExpanded = false,
@@ -432,40 +436,42 @@ function ToolUI({
432
436
  onDeny,
433
437
  }: ToolUIProps) {
434
438
  // Use annotation title if available, otherwise fall back to name
435
- const displayName = annotations?.title || name
439
+ const displayName = annotations?.title || name;
436
440
  const isApprovalPending =
437
- status === 'approval' && onApproveOnce !== undefined && onDeny !== undefined
441
+ status === "approval" &&
442
+ onApproveOnce !== undefined &&
443
+ onDeny !== undefined;
438
444
  // Auto-expand when approval is pending, collapse when approved
439
- const [isExpanded, setIsExpanded] = useState(defaultExpanded)
440
- const hasContent = request !== undefined || result !== undefined
445
+ const [isExpanded, setIsExpanded] = useState(defaultExpanded);
446
+ const hasContent = request !== undefined || result !== undefined;
441
447
 
442
448
  // Track approval mode: 'one-time' or 'for-session'
443
- const [approvalMode, setApprovalMode] = useState<ApprovalMode>('one-time')
444
- const [isDropdownOpen, setIsDropdownOpen] = useState(false)
445
- const dropdownTriggerRef = React.useRef<HTMLButtonElement>(null)
449
+ const [approvalMode, setApprovalMode] = useState<ApprovalMode>("one-time");
450
+ const [isDropdownOpen, setIsDropdownOpen] = useState(false);
451
+ const dropdownTriggerRef = React.useRef<HTMLButtonElement>(null);
446
452
 
447
453
  // Collapse when transitioning from approval to non-approval (i.e., when approved/denied)
448
454
  useEffect(() => {
449
455
  if (!isApprovalPending && isExpanded && !defaultExpanded) {
450
- setIsExpanded(false)
456
+ setIsExpanded(false);
451
457
  }
452
- }, [isApprovalPending])
458
+ }, [isApprovalPending]);
453
459
 
454
460
  // Handle approve based on selected mode
455
461
  const handleApprove = () => {
456
- if (approvalMode === 'for-session' && onApproveForSession) {
457
- onApproveForSession()
462
+ if (approvalMode === "for-session" && onApproveForSession) {
463
+ onApproveForSession();
458
464
  } else if (onApproveOnce) {
459
- onApproveOnce()
465
+ onApproveOnce();
460
466
  }
461
- }
467
+ };
462
468
 
463
469
  return (
464
470
  <div
465
471
  data-slot="tool-ui"
466
472
  className={cn(
467
- 'border-border bg-card @container overflow-hidden rounded-lg border',
468
- className
473
+ "@container overflow-hidden rounded-lg border border-border bg-card",
474
+ className,
469
475
  )}
470
476
  >
471
477
  {/* Header with provider */}
@@ -473,7 +479,7 @@ function ToolUI({
473
479
  <div
474
480
  data-slot="tool-ui-provider"
475
481
  className={cn(
476
- 'border-border flex items-center gap-2 border-b px-4 py-2.5'
482
+ "flex items-center gap-2 border-b border-border px-4 py-2.5",
477
483
  )}
478
484
  >
479
485
  {icon ? (
@@ -481,7 +487,7 @@ function ToolUI({
481
487
  {icon}
482
488
  </span>
483
489
  ) : (
484
- <span className="bg-muted flex size-5 items-center justify-center rounded text-xs font-medium">
490
+ <span className="flex size-5 items-center justify-center rounded bg-muted text-xs font-medium">
485
491
  {provider.charAt(0).toUpperCase()}
486
492
  </span>
487
493
  )}
@@ -494,15 +500,15 @@ function ToolUI({
494
500
  onClick={() => hasContent && setIsExpanded(!isExpanded)}
495
501
  disabled={!hasContent}
496
502
  className={cn(
497
- 'flex w-full items-center gap-2 px-4 py-3 text-left',
498
- hasContent && 'hover:bg-accent/50 cursor-pointer transition-colors'
503
+ "flex w-full items-center gap-2 px-4 py-3 text-left",
504
+ hasContent && "cursor-pointer transition-colors hover:bg-accent/50",
499
505
  )}
500
506
  >
501
507
  <StatusIndicator status={status} />
502
508
  <span
503
509
  className={cn(
504
- 'flex-1 text-sm',
505
- !provider && isApprovalPending && 'shimmer'
510
+ "flex-1 text-sm",
511
+ !provider && isApprovalPending && "shimmer",
506
512
  )}
507
513
  >
508
514
  {displayName}
@@ -510,8 +516,8 @@ function ToolUI({
510
516
  {hasContent && (
511
517
  <ChevronDownIcon
512
518
  className={cn(
513
- 'text-muted-foreground size-4 transition-transform duration-200',
514
- isExpanded && 'rotate-180'
519
+ "size-4 text-muted-foreground transition-transform duration-200",
520
+ isExpanded && "rotate-180",
515
521
  )}
516
522
  />
517
523
  )}
@@ -545,14 +551,14 @@ function ToolUI({
545
551
  {isApprovalPending && (
546
552
  <div
547
553
  data-slot="tool-ui-approval-actions"
548
- className="border-border flex flex-col gap-2 border-t px-4 py-3 @[320px]:flex-row @[320px]:items-center @[320px]:justify-end"
554
+ className="flex flex-col gap-2 border-t border-border px-4 py-3 @[320px]:flex-row @[320px]:items-center @[320px]:justify-end"
549
555
  >
550
556
  <div className="flex items-center gap-2 @[320px]:mr-auto">
551
- <span className="text-muted-foreground text-sm">
557
+ <span className="text-sm text-muted-foreground">
552
558
  This tool requires approval
553
559
  </span>
554
560
  {annotations?.readOnlyHint && (
555
- <span className="bg-muted text-muted-foreground rounded px-1.5 py-0.5 text-xs">
561
+ <span className="rounded bg-muted px-1.5 py-0.5 text-xs text-muted-foreground">
556
562
  Read-only
557
563
  </span>
558
564
  )}
@@ -580,16 +586,16 @@ function ToolUI({
580
586
  onClick={handleApprove}
581
587
  className="flex cursor-pointer justify-between gap-1 rounded-r-none bg-emerald-600 hover:bg-emerald-700"
582
588
  >
583
- <CheckIcon className="dark:text-foreground mr-1 size-3" />
589
+ <CheckIcon className="mr-1 size-3 dark:text-foreground" />
584
590
 
585
- <span className="dark:text-foreground @[320px]:hidden">
591
+ <span className="@[320px]:hidden dark:text-foreground">
586
592
  Approve
587
593
  </span>
588
594
  {/* The min-width is needed to prevent the button from shifting when the text changes */}
589
- <span className="dark:text-foreground hidden min-w-[110px] @[320px]:inline">
590
- {approvalMode === 'one-time'
591
- ? 'Approve this time'
592
- : 'Approve always'}
595
+ <span className="hidden min-w-[110px] @[320px]:inline dark:text-foreground">
596
+ {approvalMode === "one-time"
597
+ ? "Approve this time"
598
+ : "Approve always"}
593
599
  </span>
594
600
  </Button>
595
601
  <Popover open={isDropdownOpen}>
@@ -602,9 +608,9 @@ function ToolUI({
602
608
  onClick={() => setIsDropdownOpen((prev) => !prev)}
603
609
  >
604
610
  {isDropdownOpen ? (
605
- <ChevronUpIcon className="dark:text-foreground size-3" />
611
+ <ChevronUpIcon className="size-3 dark:text-foreground" />
606
612
  ) : (
607
- <ChevronDownIcon className="dark:text-foreground size-3" />
613
+ <ChevronDownIcon className="size-3 dark:text-foreground" />
608
614
  )}
609
615
  </Button>
610
616
  </PopoverAnchor>
@@ -615,17 +621,17 @@ function ToolUI({
615
621
  onInteractOutside={(e) => {
616
622
  // Prevent Radix auto-dismiss to avoid race condition
617
623
  // between DismissableLayer's pointerdown and button's click
618
- e.preventDefault()
624
+ e.preventDefault();
619
625
  // Use composedPath to detect trigger clicks across Shadow DOM
620
626
  const originalEvent = (
621
627
  e.detail as { originalEvent?: PointerEvent }
622
- )?.originalEvent
623
- const path = originalEvent?.composedPath?.() ?? []
628
+ )?.originalEvent;
629
+ const path = originalEvent?.composedPath?.() ?? [];
624
630
  if (
625
631
  !path.includes(dropdownTriggerRef.current as EventTarget)
626
632
  ) {
627
633
  // Clicked outside both popover and trigger - close it
628
- setIsDropdownOpen(false)
634
+ setIsDropdownOpen(false);
629
635
  }
630
636
  // If clicked on trigger, do nothing - onClick will toggle
631
637
  }}
@@ -633,20 +639,20 @@ function ToolUI({
633
639
  >
634
640
  <button
635
641
  onClick={() => {
636
- setApprovalMode('one-time')
637
- setIsDropdownOpen(false)
642
+ setApprovalMode("one-time");
643
+ setIsDropdownOpen(false);
638
644
  }}
639
- className="hover:bg-accent relative flex w-full items-start gap-2 rounded-sm px-2 py-2 text-left"
645
+ className="relative flex w-full items-start gap-2 rounded-sm px-2 py-2 text-left hover:bg-accent"
640
646
  >
641
647
  <CheckIcon
642
648
  className={cn(
643
- 'relative top-1 mt-0.5 size-3 shrink-0',
644
- approvalMode !== 'one-time' && 'invisible'
649
+ "relative top-1 mt-0.5 size-3 shrink-0",
650
+ approvalMode !== "one-time" && "invisible",
645
651
  )}
646
652
  />
647
653
  <div className="flex flex-col gap-0.5">
648
654
  <span className="text-sm">Approve only once</span>
649
- <span className="text-muted-foreground text-xs">
655
+ <span className="text-xs text-muted-foreground">
650
656
  You'll be asked again next time
651
657
  </span>
652
658
  </div>
@@ -654,20 +660,20 @@ function ToolUI({
654
660
  {onApproveForSession && (
655
661
  <button
656
662
  onClick={() => {
657
- setApprovalMode('for-session')
658
- setIsDropdownOpen(false)
663
+ setApprovalMode("for-session");
664
+ setIsDropdownOpen(false);
659
665
  }}
660
- className="hover:bg-accent relative flex w-full items-start gap-2 rounded-sm px-2 py-2 text-left"
666
+ className="relative flex w-full items-start gap-2 rounded-sm px-2 py-2 text-left hover:bg-accent"
661
667
  >
662
668
  <CheckIcon
663
669
  className={cn(
664
- 'relative top-1 mt-0.5 size-3 shrink-0',
665
- approvalMode !== 'for-session' && 'invisible'
670
+ "relative top-1 mt-0.5 size-3 shrink-0",
671
+ approvalMode !== "for-session" && "invisible",
666
672
  )}
667
673
  />
668
674
  <div className="flex flex-col gap-0.5">
669
675
  <span className="text-sm">Approve always</span>
670
- <span className="text-muted-foreground text-xs">
676
+ <span className="text-xs text-muted-foreground">
671
677
  Trust this tool for the session
672
678
  </span>
673
679
  </div>
@@ -680,7 +686,7 @@ function ToolUI({
680
686
  </div>
681
687
  )}
682
688
  </div>
683
- )
689
+ );
684
690
  }
685
691
 
686
692
  /* -----------------------------------------------------------------------------
@@ -689,59 +695,59 @@ function ToolUI({
689
695
 
690
696
  interface ToolUIGroupProps {
691
697
  /** Title for the group header */
692
- title: string
698
+ title: string;
693
699
  /** Optional icon */
694
- icon?: React.ReactNode
700
+ icon?: React.ReactNode;
695
701
  /** Overall status of the group */
696
- status?: 'running' | 'complete'
702
+ status?: "running" | "complete";
697
703
  /** Whether the group starts expanded */
698
- defaultExpanded?: boolean
704
+ defaultExpanded?: boolean;
699
705
  /** Child tool UI components */
700
- children: React.ReactNode
706
+ children: React.ReactNode;
701
707
  /** Additional class names */
702
- className?: string
708
+ className?: string;
703
709
  }
704
710
 
705
711
  function ToolUIGroup({
706
712
  title,
707
713
  icon,
708
- status = 'complete',
714
+ status = "complete",
709
715
  defaultExpanded = false,
710
716
  children,
711
717
  className,
712
718
  }: ToolUIGroupProps) {
713
- const [isExpanded, setIsExpanded] = useState(defaultExpanded)
719
+ const [isExpanded, setIsExpanded] = useState(defaultExpanded);
714
720
 
715
721
  return (
716
722
  <div
717
723
  data-slot="tool-ui-group"
718
724
  className={cn(
719
- 'border-border bg-card overflow-hidden rounded-lg border',
720
- className
725
+ "overflow-hidden rounded-lg border border-border bg-card",
726
+ className,
721
727
  )}
722
728
  >
723
729
  {/* Group header */}
724
730
  <button
725
731
  onClick={() => setIsExpanded(!isExpanded)}
726
- className="hover:bg-accent/50 flex w-full items-center gap-2 px-4 py-3 text-left transition-colors"
732
+ className="flex w-full items-center gap-2 px-4 py-3 text-left transition-colors hover:bg-accent/50"
727
733
  >
728
734
  {icon || (
729
735
  <StatusIndicator
730
- status={status === 'running' ? 'running' : 'complete'}
736
+ status={status === "running" ? "running" : "complete"}
731
737
  />
732
738
  )}
733
739
  <span
734
740
  className={cn(
735
- 'flex-1 text-sm font-medium',
736
- status === 'running' && 'shimmer'
741
+ "flex-1 text-sm font-medium",
742
+ status === "running" && "shimmer",
737
743
  )}
738
744
  >
739
745
  {title}
740
746
  </span>
741
747
  <ChevronDownIcon
742
748
  className={cn(
743
- 'text-muted-foreground size-4 transition-transform duration-200',
744
- isExpanded && 'rotate-180'
749
+ "size-4 text-muted-foreground transition-transform duration-200",
750
+ isExpanded && "rotate-180",
745
751
  )}
746
752
  />
747
753
  </button>
@@ -750,13 +756,13 @@ function ToolUIGroup({
750
756
  {isExpanded && (
751
757
  <div
752
758
  data-slot="tool-ui-group-content"
753
- className="border-border border-t"
759
+ className="border-t border-border"
754
760
  >
755
761
  {children}
756
762
  </div>
757
763
  )}
758
764
  </div>
759
- )
765
+ );
760
766
  }
761
767
 
762
768
  /* -----------------------------------------------------------------------------
@@ -770,11 +776,11 @@ export {
770
776
  SyntaxHighlightedCode,
771
777
  StatusIndicator,
772
778
  CopyButton,
773
- }
779
+ };
774
780
  export type {
775
781
  ToolUIProps,
776
782
  ToolUISectionProps,
777
783
  ToolUIGroupProps,
778
784
  ToolStatus,
779
785
  ContentItem,
780
- }
786
+ };