{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "tool-group",
  "type": "registry:component",
  "title": "Tool Group",
  "description": "Collapsible wrapper around consecutive tool calls.",
  "dependencies": [
    "@assistant-ui/react",
    "lucide-react",
    "class-variance-authority",
    "tw-shimmer"
  ],
  "registryDependencies": [
    "collapsible"
  ],
  "css": {
    "@import \"tw-shimmer\"": {},
    "@custom-variant data-open (&:where([data-state=\"open\"], [data-open]:not([data-open=\"false\"])))": {},
    "@custom-variant data-closed (&:where([data-state=\"closed\"], [data-closed]:not([data-closed=\"false\"])))": {},
    "@keyframes collapsible-down": {
      "from": {
        "height": "0"
      },
      "to": {
        "height": "var(--radix-collapsible-content-height, var(--collapsible-panel-height, auto))"
      }
    },
    "@keyframes collapsible-up": {
      "from": {
        "height": "var(--radix-collapsible-content-height, var(--collapsible-panel-height, auto))"
      },
      "to": {
        "height": "0"
      }
    }
  },
  "files": [
    {
      "type": "registry:component",
      "path": "components/assistant-ui/tool-group.tsx",
      "content": "\"use client\";\n\nimport {\n  memo,\n  useCallback,\n  useRef,\n  useState,\n  type FC,\n  type PropsWithChildren,\n} from \"react\";\nimport { ChevronDownIcon, LoaderIcon } from \"lucide-react\";\nimport { cva, type VariantProps } from \"class-variance-authority\";\nimport { useScrollLock } from \"@assistant-ui/react\";\nimport {\n  Collapsible,\n  CollapsibleContent,\n  CollapsibleTrigger,\n} from \"@/components/ui/collapsible\";\nimport { cn } from \"@/lib/utils\";\n\nconst ANIMATION_DURATION = 200;\n\nconst toolGroupVariants = cva(\"aui-tool-group-root group/tool-group w-full\", {\n  variants: {\n    variant: {\n      outline: \"rounded-lg border py-3\",\n      ghost: \"\",\n      muted: \"border-muted-foreground/30 bg-muted/30 rounded-lg border py-3\",\n    },\n  },\n  defaultVariants: { variant: \"outline\" },\n});\n\nexport type ToolGroupRootProps = Omit<\n  React.ComponentProps<typeof Collapsible>,\n  \"open\" | \"onOpenChange\"\n> &\n  VariantProps<typeof toolGroupVariants> & {\n    open?: boolean;\n    onOpenChange?: (open: boolean) => void;\n    defaultOpen?: boolean;\n  };\n\nfunction ToolGroupRoot({\n  className,\n  variant,\n  open: controlledOpen,\n  onOpenChange: controlledOnOpenChange,\n  defaultOpen = false,\n  children,\n  ...props\n}: ToolGroupRootProps) {\n  const collapsibleRef = useRef<HTMLDivElement>(null);\n  const [uncontrolledOpen, setUncontrolledOpen] = useState(defaultOpen);\n  const lockScroll = useScrollLock(collapsibleRef, ANIMATION_DURATION);\n\n  const isControlled = controlledOpen !== undefined;\n  const isOpen = isControlled ? controlledOpen : uncontrolledOpen;\n\n  const handleOpenChange = useCallback(\n    (open: boolean) => {\n      lockScroll();\n      if (!isControlled) {\n        setUncontrolledOpen(open);\n      }\n      controlledOnOpenChange?.(open);\n    },\n    [lockScroll, isControlled, controlledOnOpenChange],\n  );\n\n  return (\n    <Collapsible\n      ref={collapsibleRef}\n      data-slot=\"tool-group-root\"\n      data-variant={variant ?? \"outline\"}\n      open={isOpen}\n      onOpenChange={handleOpenChange}\n      className={cn(\n        toolGroupVariants({ variant }),\n        \"group/tool-group-root\",\n        className,\n      )}\n      style={\n        {\n          \"--animation-duration\": `${ANIMATION_DURATION}ms`,\n        } as React.CSSProperties\n      }\n      {...props}\n    >\n      {children}\n    </Collapsible>\n  );\n}\n\nfunction ToolGroupTrigger({\n  count,\n  active = false,\n  className,\n  ...props\n}: React.ComponentProps<typeof CollapsibleTrigger> & {\n  count: number;\n  active?: boolean;\n}) {\n  const label = `${count} tool ${count === 1 ? \"call\" : \"calls\"}`;\n\n  return (\n    <CollapsibleTrigger\n      data-slot=\"tool-group-trigger\"\n      className={cn(\n        \"aui-tool-group-trigger group/trigger flex origin-left items-center gap-2 text-sm transition-[color,scale] active:scale-[0.98]\",\n        \"group-data-[variant=ghost]/tool-group-root:text-muted-foreground group-data-[variant=ghost]/tool-group-root:hover:text-foreground group-data-[variant=ghost]/tool-group-root:py-1.5\",\n        \"group-data-[variant=outline]/tool-group-root:w-full group-data-[variant=outline]/tool-group-root:px-4\",\n        \"group-data-[variant=muted]/tool-group-root:w-full group-data-[variant=muted]/tool-group-root:px-4\",\n        className,\n      )}\n      {...props}\n    >\n      {active && (\n        <LoaderIcon\n          data-slot=\"tool-group-trigger-loader\"\n          className=\"aui-tool-group-trigger-loader size-3 shrink-0 animate-spin [animation-duration:0.6s]\"\n        />\n      )}\n      <span\n        data-slot=\"tool-group-trigger-label\"\n        className={cn(\n          \"aui-tool-group-trigger-label-wrapper relative inline-block text-start leading-none font-medium\",\n          \"group-data-[variant=ghost]/tool-group-root:font-normal\",\n          \"group-data-[variant=outline]/tool-group-root:grow\",\n          \"group-data-[variant=muted]/tool-group-root:grow\",\n        )}\n      >\n        <span className=\"text-xs\">{label}</span>\n        {active && (\n          <span\n            aria-hidden\n            data-slot=\"tool-group-trigger-shimmer\"\n            className=\"aui-tool-group-trigger-shimmer shimmer pointer-events-none absolute inset-0 text-xs motion-reduce:animate-none\"\n          >\n            {label}\n          </span>\n        )}\n      </span>\n      <ChevronDownIcon\n        data-slot=\"tool-group-trigger-chevron\"\n        className={cn(\n          \"aui-tool-group-trigger-chevron size-3 shrink-0\",\n          \"transition-transform duration-(--animation-duration) ease-[cubic-bezier(0.32,0.72,0,1)] motion-reduce:transition-none\",\n          \"-rotate-90\",\n          \"group-data-open/trigger:rotate-0\",\n          \"group-data-panel-open/trigger:rotate-0\",\n        )}\n      />\n    </CollapsibleTrigger>\n  );\n}\n\nfunction ToolGroupContent({\n  className,\n  children,\n  ...props\n}: React.ComponentProps<typeof CollapsibleContent>) {\n  return (\n    <CollapsibleContent\n      data-slot=\"tool-group-content\"\n      className={cn(\n        \"aui-tool-group-content relative overflow-hidden text-sm outline-none\",\n        \"group/collapsible-content ease-[cubic-bezier(0.32,0.72,0,1)] motion-reduce:animate-none\",\n        \"data-closed:animate-collapsible-up\",\n        \"data-open:animate-collapsible-down\",\n        \"data-closed:fill-mode-forwards\",\n        \"data-closed:pointer-events-none\",\n        \"data-open:duration-(--animation-duration)\",\n        \"data-closed:duration-(--animation-duration)\",\n        className,\n      )}\n      {...props}\n    >\n      <div\n        className={cn(\n          \"mt-2 flex flex-col gap-2\",\n          \"group-data-[variant=ghost]/tool-group-root:mt-1 group-data-[variant=ghost]/tool-group-root:gap-1\",\n          \"group-data-[variant=outline]/tool-group-root:mt-3 group-data-[variant=outline]/tool-group-root:border-t group-data-[variant=outline]/tool-group-root:px-4 group-data-[variant=outline]/tool-group-root:pt-3\",\n          \"group-data-[variant=muted]/tool-group-root:mt-3 group-data-[variant=muted]/tool-group-root:border-t group-data-[variant=muted]/tool-group-root:px-4 group-data-[variant=muted]/tool-group-root:pt-3\",\n          \"[&>*]:animate-in [&>*]:fade-in-0 [&>*]:blur-in-[2px] [&>*]:slide-in-from-top-1 [&>*]:duration-(--animation-duration) [&>*]:ease-[cubic-bezier(0.32,0.72,0,1)]\",\n          \"[&>*]:motion-reduce:animate-none\",\n          \"[&>*:nth-child(2)]:[animation-delay:40ms]\",\n          \"[&>*:nth-child(3)]:[animation-delay:80ms]\",\n          \"[&>*:nth-child(4)]:[animation-delay:120ms]\",\n          \"[&>*:nth-child(n+5)]:[animation-delay:160ms]\",\n        )}\n      >\n        {children}\n      </div>\n    </CollapsibleContent>\n  );\n}\n\ntype ToolGroupComponent = FC<\n  PropsWithChildren<{ startIndex: number; endIndex: number }>\n> & {\n  Root: typeof ToolGroupRoot;\n  Trigger: typeof ToolGroupTrigger;\n  Content: typeof ToolGroupContent;\n};\n\nconst ToolGroupImpl: FC<\n  PropsWithChildren<{ startIndex: number; endIndex: number }>\n> = ({ children, startIndex, endIndex }) => {\n  const toolCount = endIndex - startIndex + 1;\n\n  return (\n    <ToolGroupRoot>\n      <ToolGroupTrigger count={toolCount} />\n      <ToolGroupContent>{children}</ToolGroupContent>\n    </ToolGroupRoot>\n  );\n};\n\n/**\n * @deprecated This wrapper targets the legacy `components.ToolGroup` prop\n * on `<MessagePrimitive.Parts>`. Use `<MessagePrimitive.GroupedParts>` with\n * a `groupBy` returning `\"group-tool\"` and compose `ToolGroupRoot` /\n * `ToolGroupTrigger` / `ToolGroupContent` directly. See `thread.tsx`.\n */\nconst ToolGroup = memo(ToolGroupImpl) as unknown as ToolGroupComponent;\n\nToolGroup.displayName = \"ToolGroup\";\nToolGroup.Root = ToolGroupRoot;\nToolGroup.Trigger = ToolGroupTrigger;\nToolGroup.Content = ToolGroupContent;\n\nexport {\n  ToolGroup,\n  ToolGroupRoot,\n  ToolGroupTrigger,\n  ToolGroupContent,\n  toolGroupVariants,\n};\n"
    }
  ]
}