{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "plan-tool",
  "type": "registry:ui",
  "dependencies": [
    "@tabler/icons-react",
    "clsx",
    "tailwind-merge"
  ],
  "registryDependencies": [
    "https://agent-elements.21st.dev/r/markdown.json"
  ],
  "files": [
    {
      "path": "registry/agent-elements/components/tools/plan-tool.tsx",
      "content": "import { memo, useState } from \"react\";\nimport {\n  IconChevronsDown,\n  IconChevronsUp,\n  IconFileDescription,\n} from \"@tabler/icons-react\";\nimport { Markdown } from \"../markdown\";\nimport { IconSpinner } from \"../icons\";\nimport { areToolPropsEqual, getToolStatus } from \"../utils/format-tool\";\nimport { cn } from \"../utils/cn\";\n\nexport type Plan = {\n  id?: string;\n  title: string;\n  summary?: string;\n};\n\nexport type PlanToolProps = {\n  part: {\n    type: string;\n    toolCallId?: string;\n    state?: string;\n    input?: {\n      plan?: Plan;\n      onApprove?: () => void;\n      approveLabel?: string;\n      approved?: boolean;\n    };\n  };\n  chatStatus?: string;\n};\n\nfunction getPlanFileName(plan: Plan) {\n  const rawId = plan.id?.trim();\n  if (!rawId) return \"plan-working.md\";\n  if (rawId.endsWith(\".md\")) return rawId;\n  return `plan-${rawId}.md`;\n}\n\nexport const PlanTool = memo(function PlanTool({\n  part,\n  chatStatus,\n}: PlanToolProps) {\n  const { isPending } = getToolStatus(part, chatStatus);\n  const plan = part.input?.plan;\n  const [isExpanded, setIsExpanded] = useState(false);\n  const [isApproved, setIsApproved] = useState(false);\n\n  if (!plan) return null;\n\n  const fileName = getPlanFileName(plan);\n  const summary = plan.summary?.trim() ?? \"\";\n  const hasSummary = summary.length > 0;\n\n  const approveLabel = part.input?.approveLabel ?? \"Approve\";\n  const isAlreadyApproved = part.input?.approved || isApproved;\n  const approveText = isAlreadyApproved ? \"Approved\" : approveLabel;\n\n  const handleApprove = () => {\n    if (isAlreadyApproved) return;\n    setIsApproved(true);\n    if (typeof part.input?.onApprove === \"function\") {\n      part.input.onApprove();\n    }\n  };\n\n  return (\n    <div className=\"an-tool-plan rounded-an-tool-border-radius border border-border bg-an-tool-background overflow-hidden\">\n      <div className=\"h-7 pl-3 pr-2.5 flex items-center justify-between\">\n        <div className=\"min-w-0 flex items-center gap-1\">\n          {isPending ? (\n            <IconSpinner className=\"w-3 h-3 text-an-tool-color-muted animate-spin shrink-0\" />\n          ) : (\n            <IconFileDescription className=\"w-3.5 h-3.5 text-an-tool-color-muted shrink-0\" />\n          )}\n          <span className=\"text-xs text-an-tool-color-muted truncate\">\n            {fileName}\n          </span>\n        </div>\n        <button\n          type=\"button\"\n          onClick={() => setIsExpanded((prev) => !prev)}\n          aria-label={isExpanded ? \"Collapse plan\" : \"Expand plan\"}\n          className=\"size-5 inline-flex items-center justify-center text-an-tool-color-muted\"\n        >\n          {isExpanded ? (\n            <IconChevronsUp className=\"w-3.5 h-3.5\" />\n          ) : (\n            <IconChevronsDown className=\"w-3.5 h-3.5\" />\n          )}\n        </button>\n      </div>\n\n      <div className=\"border-t border-border bg-background pt-2\">\n        <div className=\"space-y-1.5\">\n          <div className=\"text-sm text-an-tool-color px-3\">{plan.title}</div>\n\n          {hasSummary ? (\n            <div className=\"relative\">\n              <div\n                className={cn(\n                  \"px-3\",\n                  \"text-sm text-an-tool-color-muted\",\n                  !isExpanded && \"max-h-[94px] overflow-hidden\",\n                )}\n              >\n                <Markdown content={summary} className=\"text-sm\" />\n              </div>\n\n              {!isExpanded && (\n                <div className=\"absolute inset-x-0 bottom-0 h-16 pb-2 pl-3.5 pr-2\">\n                  <div className=\"absolute inset-x-0 bottom-0 h-full w-full bg-linear-to-b from-transparent from-0% to-background to-50%\" />\n                  <div className=\"h-full flex items-end justify-between relative\">\n                    <button\n                      type=\"button\"\n                      onClick={() => setIsExpanded(true)}\n                      className=\"-mx-2 h-5 px-1.5 rounded-[4px] text-xs text-muted-foreground hover:text-an-tool-color\"\n                    >\n                      Read detailed plan\n                    </button>\n                    {!isAlreadyApproved && (\n                      <button\n                        type=\"button\"\n                        onClick={handleApprove}\n                        className=\"h-5 px-1.5 rounded-[4px] text-xs font-medium bg-an-primary-color text-an-send-button-color hover:bg-an-primary-color/90 active:scale-[0.98] transition-[background-color,transform] duration-150\"\n                      >\n                        {approveText}\n                      </button>\n                    )}\n                  </div>\n                </div>\n              )}\n            </div>\n          ) : (\n            <div className=\"text-xs text-an-tool-color-muted\">\n              No plan summary provided.\n            </div>\n          )}\n        </div>\n\n        {(isExpanded || !hasSummary) && (\n          <div className=\"mt-2 flex items-center justify-between pt-1.5 pb-2 pl-3.5 pr-2 border-t border-border bg-an-tool-background\">\n            <button\n              type=\"button\"\n              onClick={() => setIsExpanded((prev) => !prev)}\n              className=\"-mx-2 h-5 px-1.5 rounded-[4px] text-xs text-muted-foreground hover:text-an-tool-color\"\n            >\n              {isExpanded ? \"Hide detailed plan\" : \"Read detailed plan\"}\n            </button>\n            {!isAlreadyApproved && (\n              <button\n                type=\"button\"\n                onClick={handleApprove}\n                className=\"h-5 px-1.5 rounded-[4px] text-xs font-medium bg-an-primary-color text-an-send-button-color hover:bg-an-primary-color/90 active:scale-[0.98] transition-[background-color,transform] duration-150\"\n              >\n                {approveText}\n              </button>\n            )}\n          </div>\n        )}\n      </div>\n    </div>\n  );\n}, areToolPropsEqual);\n",
      "type": "registry:ui",
      "target": "components/agent-elements/tools/plan-tool.tsx"
    },
    {
      "path": "registry/agent-elements/icons.tsx",
      "content": "import React from \"react\";\n\ntype IconProps = React.SVGProps<SVGSVGElement> & {\n  className?: string;\n};\n\nexport function IconSpinner({ className, ...rest }: IconProps) {\n  return (\n    <svg\n      viewBox=\"0 0 24 24\"\n      width=\"16\"\n      height=\"16\"\n      fill=\"none\"\n      className={\n        className ?? \"animate-spin will-change-transform text-muted-foreground\"\n      }\n      {...rest}\n    >\n      <circle\n        cx=\"12\"\n        cy=\"12\"\n        r=\"10\"\n        stroke=\"currentColor\"\n        strokeWidth=\"3\"\n        strokeLinecap=\"round\"\n        fill=\"none\"\n        opacity={0.2}\n      />\n      <path\n        d=\"M12 2C6.48 2 2 6.48 2 12\"\n        stroke=\"currentColor\"\n        strokeWidth=\"3\"\n        strokeLinecap=\"round\"\n        fill=\"none\"\n      />\n    </svg>\n  );\n}\n\nexport function CheckIcon(props: IconProps) {\n  return (\n    <svg\n      xmlns=\"http://www.w3.org/2000/svg\"\n      viewBox=\"0 0 24 24\"\n      fill=\"none\"\n      {...props}\n    >\n      <path\n        d=\"M5 12.75L10 19L19 5\"\n        stroke=\"currentColor\"\n        strokeWidth=\"2\"\n        strokeLinecap=\"round\"\n        strokeLinejoin=\"round\"\n      />\n    </svg>\n  );\n}\n\nexport function IconArrowRight(props: IconProps) {\n  return (\n    <svg viewBox=\"0 0 24 24\" fill=\"none\" width=\"24\" height=\"24\" {...props}>\n      <path\n        d=\"M14 6L20 12L14 18\"\n        stroke=\"currentColor\"\n        strokeWidth=\"2\"\n        strokeLinecap=\"round\"\n        strokeLinejoin=\"round\"\n      />\n      <path\n        d=\"M19 12H4\"\n        stroke=\"currentColor\"\n        strokeWidth=\"2\"\n        strokeLinecap=\"round\"\n      />\n    </svg>\n  );\n}\n\nexport function IconDoubleChevronRight(props: IconProps) {\n  return (\n    <svg viewBox=\"0 0 24 24\" fill=\"none\" width=\"16\" height=\"16\" {...props}>\n      <path\n        d=\"M6 17L11 12L6 7M13 17L18 12L13 7\"\n        stroke=\"currentColor\"\n        strokeWidth=\"2\"\n        strokeLinecap=\"round\"\n        strokeLinejoin=\"round\"\n      />\n    </svg>\n  );\n}\n",
      "type": "registry:ui",
      "target": "components/agent-elements/icons.tsx"
    },
    {
      "path": "registry/agent-elements/utils/format-tool.ts",
      "content": "/**\n * Tool state cache for detecting AI SDK in-place mutations.\n * AI SDK mutates objects in-place during streaming, so we must\n * cache state externally and compare cached values.\n */\n\ntype CachedToolState = {\n  state: string | undefined;\n  inputJson: string;\n  outputJson: string;\n};\n\nconst toolStateCache = new Map<string, CachedToolState>();\n\nfunction getToolStateSnapshot(part: any): CachedToolState {\n  return {\n    state: part.state,\n    inputJson: JSON.stringify(part.input || {}),\n    outputJson: JSON.stringify(part.output || {}),\n  };\n}\n\nfunction hasToolStateChanged(toolCallId: string, part: any): boolean {\n  const cached = toolStateCache.get(toolCallId);\n  const current = getToolStateSnapshot(part);\n\n  if (!cached) {\n    toolStateCache.set(toolCallId, current);\n    return true;\n  }\n\n  const changed =\n    cached.state !== current.state ||\n    cached.inputJson !== current.inputJson ||\n    cached.outputJson !== current.outputJson;\n\n  if (changed) {\n    toolStateCache.set(toolCallId, current);\n  }\n\n  return changed;\n}\n\nfunction arePartsEqual(prev: any, next: any): boolean {\n  if (prev.toolCallId !== next.toolCallId) return false;\n  if (prev.type !== next.type) return false;\n\n  const toolCallId = next.toolCallId;\n  if (!toolCallId) {\n    return prev.state === next.state;\n  }\n\n  const changed = hasToolStateChanged(toolCallId, next);\n  return !changed;\n}\n\nfunction isToolCompleted(part: any): boolean {\n  if (part.output !== undefined && part.output !== null) return true;\n  if (part.state === \"error\") return true;\n  if (part.state === \"result\") return true;\n  return false;\n}\n\n/** Deep compare function for tool part props. Used with React.memo(). */\nexport function areToolPropsEqual(\n  prevProps: { part: any; chatStatus?: string },\n  nextProps: { part: any; chatStatus?: string },\n): boolean {\n  const partsEqual = arePartsEqual(prevProps.part, nextProps.part);\n  if (!partsEqual) return false;\n  if (isToolCompleted(nextProps.part)) return true;\n  if (prevProps.chatStatus !== nextProps.chatStatus) return false;\n  return true;\n}\n\n/** Get tool status from part state */\nexport function getToolStatus(part: any, chatStatus?: string) {\n  const basePending =\n    part.state !== \"output-available\" && part.state !== \"output-error\";\n  const isError =\n    part.state === \"output-error\" ||\n    (part.state === \"output-available\" && part.output?.success === false);\n  const isSuccess = part.state === \"output-available\" && !isError;\n  const isPending = basePending && chatStatus === \"streaming\";\n  const isInterrupted =\n    basePending && chatStatus !== \"streaming\" && chatStatus !== undefined;\n\n  return { isPending, isError, isSuccess, isInterrupted };\n}\n",
      "type": "registry:lib",
      "target": "components/agent-elements/utils/format-tool.ts"
    },
    {
      "path": "registry/agent-elements/utils/cn.ts",
      "content": "import { clsx, type ClassValue } from \"clsx\";\nimport { twMerge } from \"tailwind-merge\";\n\nexport function cn(...inputs: ClassValue[]) {\n  return twMerge(clsx(inputs));\n}\n",
      "type": "registry:lib",
      "target": "components/agent-elements/utils/cn.ts"
    },
    {
      "path": "registry/agent-elements/agent-ui.css",
      "content": "/* Agent Elements tokens + utilities */\n\n/*\n * AN Agent Chat - CSS Custom Properties\n * These are the default values. Override by redefining these vars in your app CSS.\n */\n:root {\n  /* Geometry — all radii derive from --an-border-radius */\n  --an-border-radius: 16px;\n  --an-message-border-radius: var(--an-border-radius);\n  --an-input-border-radius: var(--an-border-radius);\n  --an-tool-border-radius: 10px;\n  --an-message-radius-inner-offset: 4px;\n  --an-max-width: 420px;\n\n  /* Colors - Light mode defaults */\n  --an-background: #ffffff;\n  --an-background-secondary: #f0f0f0;\n  --an-background-tertiary: #f8f8f8;\n  --an-foreground: #1a1a1a;\n  --an-foreground-muted: #737373;\n  --an-foreground-subtle: #a3a3a3;\n  --an-border-color: #e4e4e7;\n  --an-primary-color: #3b82f6;\n\n  /* User Messages */\n  --an-user-message-bg: #f5f5f5;\n  --an-user-message-text: #1a1a1a;\n\n  /* Input */\n  --an-input-background: #ffffff;\n  --an-input-border-color: #e4e4e7;\n  --an-input-color: #1a1a1a;\n  --an-input-placeholder-color: #a3a3a3;\n  --an-input-focus-outline: transparent;\n  --an-context-padding: 10px;\n\n  /* Send/Stop Buttons */\n  --an-send-button-bg: #3b82f6;\n  --an-send-button-color: #ffffff;\n\n  /* Tools */\n  --an-tool-background: #f5f5f5;\n  --an-tool-border-color: #e4e4e7;\n  --an-tool-color: #1a1a1a;\n  --an-tool-color-muted: #737373;\n\n  /* Code */\n  --an-code-background: #1e1e1e;\n  --an-code-color: #d4d4d4;\n\n  /* Diff colors */\n  --an-diff-added-bg: rgba(34, 197, 94, 0.1);\n  --an-diff-added-border: rgba(34, 197, 94, 0.5);\n  --an-diff-added-text: #15803d;\n  --an-diff-removed-bg: rgba(239, 68, 68, 0.1);\n  --an-diff-removed-border: rgba(239, 68, 68, 0.5);\n  --an-diff-removed-text: #dc2626;\n}\n\n/* Dark mode defaults */\n.dark {\n  --an-background: #0a0a0a;\n  --an-background-secondary: #242424;\n  --an-background-tertiary: #141414;\n  --an-foreground: #fafafa;\n  --an-foreground-muted: #8c8c8c;\n  --an-foreground-subtle: #71717a;\n  --an-border-color: #2a2a2a;\n  --an-primary-color: #60a5fa;\n\n  --an-user-message-bg: #1a1a1a;\n  --an-user-message-text: #fafafa;\n\n  --an-input-background: #0a0a0a;\n  --an-input-border-color: #2a2a2a;\n  --an-input-color: #fafafa;\n  --an-input-placeholder-color: #71717a;\n  --an-input-focus-outline: transparent;\n  --an-context-padding: 12px;\n\n  --an-send-button-bg: #60a5fa;\n  /* Dark mode uses a lighter primary (#60a5fa) — pair it with black text so\n     labels like \"Approve\" / send-arrow have proper contrast instead of\n     low-contrast white-on-light-blue. */\n  --an-send-button-color: #0a0a0a;\n\n  --an-tool-background: #141414;\n  --an-tool-border-color: #2a2a2a;\n  --an-tool-color: #fafafa;\n  --an-tool-color-muted: #8c8c8c;\n\n  --an-code-background: #0a0a0a;\n  --an-code-color: #d4d4d4;\n\n  --an-diff-added-bg: rgba(34, 197, 94, 0.15);\n  --an-diff-added-border: rgba(34, 197, 94, 0.4);\n  --an-diff-added-text: #4ade80;\n  --an-diff-removed-bg: rgba(239, 68, 68, 0.15);\n  --an-diff-removed-border: rgba(239, 68, 68, 0.4);\n  --an-diff-removed-text: #f87171;\n}\n\n@theme inline {\n  --color-an-background: var(--an-background);\n  --color-an-background-secondary: var(--an-background-secondary);\n  --color-an-background-tertiary: var(--an-background-tertiary);\n  --color-an-foreground: var(--an-foreground);\n  --color-an-foreground-muted: var(--an-foreground-muted);\n  --color-an-foreground-subtle: var(--an-foreground-subtle);\n  --color-an-border-color: var(--an-border-color);\n  --color-an-primary-color: var(--an-primary-color);\n  --color-an-user-message-bg: var(--an-user-message-bg);\n  --color-an-user-message-text: var(--an-user-message-text);\n  --color-an-input-background: var(--an-input-background);\n  --color-an-input-border-color: var(--an-input-border-color);\n  --color-an-input-color: var(--an-input-color);\n  --color-an-input-placeholder-color: var(--an-input-placeholder-color);\n  --color-an-input-focus-outline: var(--an-input-focus-outline);\n  --color-an-send-button-bg: var(--an-send-button-bg);\n  --color-an-send-button-color: var(--an-send-button-color);\n  --color-an-tool-background: var(--an-tool-background);\n  --color-an-tool-border-color: var(--an-tool-border-color);\n  --color-an-tool-color: var(--an-tool-color);\n  --color-an-tool-color-muted: var(--an-tool-color-muted);\n  --color-an-code-background: var(--an-code-background);\n  --color-an-code-color: var(--an-code-color);\n  --color-an-diff-added-bg: var(--an-diff-added-bg);\n  --color-an-diff-added-border: var(--an-diff-added-border);\n  --color-an-diff-added-text: var(--an-diff-added-text);\n  --color-an-diff-removed-bg: var(--an-diff-removed-bg);\n  --color-an-diff-removed-border: var(--an-diff-removed-border);\n  --color-an-diff-removed-text: var(--an-diff-removed-text);\n  --radius-an-message: var(--an-message-border-radius);\n  --radius-an-message-inner: calc(\n    var(--an-message-border-radius) - var(--an-message-radius-inner-offset)\n  );\n  --radius-an-input-border-radius: var(--an-input-border-radius);\n  --radius-an-tool-border-radius: var(--an-tool-border-radius);\n  --spacing-an-context-padding: var(--an-context-padding);\n  --max-width-an: var(--an-max-width);\n  --spacing-an-user-message-x: 14px;\n  --spacing-an-user-message-y: 10px;\n}\n\n/* TextShimmer animation */\n@keyframes an-shimmer {\n  from {\n    background-position: 100% center;\n  }\n  to {\n    background-position: 0% center;\n  }\n}\n\n@keyframes an-blink {\n  50% {\n    opacity: 0;\n  }\n}\n\n@keyframes loading-dots {\n  0%,\n  100% {\n    opacity: 0;\n  }\n  50% {\n    opacity: 1;\n  }\n}\n\n@keyframes an-ellipsis {\n  0% {\n    width: 0;\n  }\n  33% {\n    width: 0.33em;\n  }\n  66% {\n    width: 0.66em;\n  }\n  100% {\n    width: 1em;\n  }\n}\n\n.an-text-shimmer {\n  display: inline-block;\n  background-size: 250% 100%;\n  background-clip: text;\n  -webkit-background-clip: text;\n  color: transparent;\n  background-image: linear-gradient(\n    90deg,\n    var(--an-foreground-subtle, #a3a3a3) 0%,\n    var(--an-foreground-subtle, #a3a3a3) 40%,\n    var(--an-foreground-muted, #737373) 50%,\n    var(--an-foreground-subtle, #a3a3a3) 60%,\n    var(--an-foreground-subtle, #a3a3a3) 100%\n  );\n  background-repeat: no-repeat;\n}\n\n.an-text-shimmer--active {\n  animation: an-shimmer var(--an-shimmer-duration, 2s) linear infinite;\n}\n\n.an-ellipsis {\n  display: inline-block;\n  overflow: hidden;\n  width: 0;\n  vertical-align: bottom;\n  animation: an-ellipsis 1.2s steps(1, end) infinite;\n}\n\n.an-markdown pre code {\n  counter-reset: none !important;\n}\n\n.an-markdown pre code > span::before {\n  content: none !important;\n  display: none !important;\n}\n\n.an-markdown pre code > span {\n  padding-left: 0 !important;\n}\n\n/* Diff */\n.an-edit-diff,\n.an-edit-diff pre,\n.an-edit-diff code {\n  font-size: 12px;\n}\n\n.dark .an-edit-tool-card {\n  background-color: #000;\n}\n\n[data-theme=\"dark\"] .an-edit-tool-card {\n  background-color: #000;\n}\n\n.dark .an-edit-tool-card .an-edit-diff,\n.dark .an-edit-tool-card .an-edit-diff pre,\n.dark .an-edit-tool-card .an-edit-diff code {\n  background-color: #000 !important;\n}\n\n[data-theme=\"dark\"] .an-edit-tool-card .an-edit-diff,\n[data-theme=\"dark\"] .an-edit-tool-card .an-edit-diff pre,\n[data-theme=\"dark\"] .an-edit-tool-card .an-edit-diff code {\n  background-color: #000 !important;\n}\n\n/* Markdown Code Block */\n.an-markdown [data-streamdown=\"code-block\"] {\n  padding: 0;\n  border: 1px solid var(--an-border-color);\n  border-radius: var(--an-tool-border-radius);\n  background: transparent;\n  gap: 0;\n}\n\n.an-markdown [data-streamdown=\"code-block-header\"] {\n  padding: 0px 10px 0px 8px;\n  height: auto;\n  font-size: 12px;\n  height: 28px;\n  background: transparent;\n  border-bottom: 1px solid var(--color-an-tool-border-color);\n  background-color: var(--an-tool-background);\n  position: relative;\n}\n\n.an-markdown div:has(> [data-streamdown=\"code-block-actions\"]) {\n  position: absolute;\n  height: auto;\n  margin: 0;\n  top: 0;\n  right: 0;\n  height: 28px;\n  padding-right: 8px;\n}\n\n.an-markdown [data-streamdown=\"code-block-actions\"] {\n  background: transparent;\n  border: none;\n  backdrop-filter: none;\n  padding: 0;\n}\n\n.an-markdown [data-streamdown=\"code-block-actions\"] button > svg {\n  width: 14px;\n  height: 14px;\n}\n\n.an-markdown [data-streamdown=\"code-block-body\"] {\n  margin: 0;\n  padding-top: 8px;\n  padding-bottom: 8px;\n  background: transparent;\n  border: transparent;\n  overflow-x: auto;\n}\n\n.an-markdown [data-streamdown=\"code-block\"] pre,\n.an-markdown [data-streamdown=\"code-block\"] code {\n  font-size: 12px;\n  background: transparent;\n}\n",
      "type": "registry:style",
      "target": "components/agent-elements/agent-ui.css"
    }
  ],
  "css": {
    "@import \"../components/agent-elements/agent-ui.css\"": ""
  }
}