{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "generative-ui",
  "title": "Generative UI",
  "description": "A production-ready AI SDK UI generative interface demo with OpenAI hosted web search and model-selected React components.",
  "dependencies": [
    "@ai-sdk/openai",
    "@ai-sdk/react",
    "@base-ui/react",
    "ai",
    "class-variance-authority",
    "lucide-react",
    "zod"
  ],
  "registryDependencies": [
    "utils",
    "badge",
    "breadcrumb",
    "button",
    "card",
    "separator",
    "table",
    "textarea",
    "tooltip",
    "https://elements.ai-sdk.dev/api/registry/conversation.json",
    "https://elements.ai-sdk.dev/api/registry/message.json",
    "https://elements.ai-sdk.dev/api/registry/reasoning.json",
    "https://elements.ai-sdk.dev/api/registry/sources.json",
    "https://elements.ai-sdk.dev/api/registry/tool.json"
  ],
  "files": [
    {
      "path": "registry/generative-ui/app/demos/generative-ui/page.tsx",
      "content": "import { GenerativeUiScreen } from \"@/components/generative-ui/generative-ui-screen\";\n\nexport default function GenerativeUiPage() {\n  return <GenerativeUiScreen />;\n}\n",
      "type": "registry:page",
      "target": "app/demos/generative-ui/page.tsx"
    },
    {
      "path": "registry/generative-ui/app/api/demos/generative-ui/route.ts",
      "content": "import { handleGenerativeUiRequest } from \"@/lib/generative-ui/runtime\";\n\nexport const runtime = \"nodejs\";\n\nexport function POST(request: Request) {\n  return handleGenerativeUiRequest(request);\n}\n",
      "type": "registry:page",
      "target": "app/api/demos/generative-ui/route.ts"
    },
    {
      "path": "registry/generative-ui/components/generative-ui/generative-ui-screen.tsx",
      "content": "import { TooltipProvider } from \"@/components/ui/tooltip\";\n\nimport { DemoWorkspaceShell } from \"@/components/demo-workspace-shell\";\nimport { getGenerativeUiRuntimeState } from \"@/lib/generative-ui/runtime\";\nimport { GenerativeUiWorkspace } from \"@/components/generative-ui/generative-ui-workspace\";\n\nexport function GenerativeUiScreen() {\n  const runtimeState = getGenerativeUiRuntimeState();\n\n  return (\n    <TooltipProvider>\n      <DemoWorkspaceShell\n        badges={[runtimeState.statusLabel, runtimeState.chatModel]}\n        breadcrumbClassName=\"font-heading text-xs tracking-[0.16em]\"\n        breadcrumbTitle=\"Generative UI\"\n        headerFrame=\"card\"\n        summary=\"A chat workspace where the model can use hosted web search when recency matters, then render either a comparison matrix or recommendation card through AI SDK UI tool parts.\"\n        title=\"Model-selected interface components inside the message stream\"\n      >\n        <GenerativeUiWorkspace\n          chatModel={runtimeState.chatModel}\n          isChatAvailable={runtimeState.isChatAvailable}\n          nodeVersion={runtimeState.nodeVersion}\n          setupMessage={runtimeState.setupMessage}\n        />\n      </DemoWorkspaceShell>\n    </TooltipProvider>\n  );\n}\n",
      "type": "registry:component",
      "target": "@components/generative-ui/generative-ui-screen.tsx"
    },
    {
      "path": "registry/generative-ui/components/generative-ui/generative-ui-workspace.tsx",
      "content": "\"use client\";\n\nimport {\n  Conversation,\n  ConversationContent,\n  ConversationEmptyState,\n  ConversationScrollButton,\n} from \"@/components/ai-elements/conversation\";\nimport {\n  Message,\n  MessageContent,\n  MessageResponse,\n} from \"@/components/ai-elements/message\";\nimport {\n  PromptInput,\n  PromptInputBody,\n  PromptInputFooter,\n  PromptInputSubmit,\n  PromptInputTextarea,\n} from \"@/components/ai-elements/prompt-input\";\nimport {\n  Reasoning,\n  ReasoningContent,\n  ReasoningTrigger,\n} from \"@/components/ai-elements/reasoning\";\nimport {\n  Source,\n  Sources,\n  SourcesContent,\n  SourcesTrigger,\n} from \"@/components/ai-elements/sources\";\nimport {\n  Tool,\n  ToolContent,\n  ToolHeader,\n  ToolInput,\n  ToolOutput,\n} from \"@/components/ai-elements/tool\";\nimport { Badge } from \"@/components/ui/badge\";\nimport { Button } from \"@/components/ui/button\";\nimport { Separator } from \"@/components/ui/separator\";\nimport type { UIMessage } from \"ai\";\nimport {\n  BotIcon,\n  RefreshCwIcon,\n  SearchIcon,\n  SparklesIcon,\n  SquareIcon,\n} from \"lucide-react\";\nimport { type ReactNode, useState } from \"react\";\n\nimport {\n  ConversationErrorMessage,\n  useConversationErrorRetry,\n} from \"@/components/demo-chat/conversation-error-message\";\n\nimport { FeatureComparisonMatrix } from \"@/components/generative-ui/feature-comparison-matrix\";\nimport {\n  featureComparisonToolPartType,\n  type GenerativeUiToolPart,\n  planRecommendationToolPartType,\n  projectGenerativeUiMessage,\n} from \"@/components/generative-ui/message-parts\";\nimport { PlanRecommendationCard } from \"@/components/generative-ui/plan-recommendation-card\";\nimport { useGenerativeUiChat } from \"@/components/generative-ui/use-generative-ui-chat\";\n\nconst generativeUiSamplePrompts = [\n  \"Compare the current AI app builder options for a small SaaS team.\",\n  \"Which AI coding assistant should I pick this month for a TypeScript monorepo?\",\n  \"Compare RAG, tool calling, and workflow agents for a support chatbot.\",\n  \"Recommend the best agent pattern for a demo that must ship in 30 minutes.\",\n] as const;\n\nfunction getLatestAssistantMessage(messages: UIMessage[]) {\n  for (let index = messages.length - 1; index >= 0; index -= 1) {\n    const message = messages[index];\n\n    if (message?.role === \"assistant\") {\n      return message;\n    }\n  }\n\n  return;\n}\n\nfunction getToolDisplayTitle(partType: string) {\n  if (partType === \"tool-web_search\") {\n    return \"Web search\";\n  }\n\n  return \"Auxiliary tool\";\n}\n\nfunction getGenerativeUiToolOutputTitle(partType: string) {\n  if (partType === featureComparisonToolPartType) {\n    return \"Feature comparison tool output\";\n  }\n\n  if (partType === planRecommendationToolPartType) {\n    return \"Plan recommendation tool output\";\n  }\n\n  return \"Generative UI tool output\";\n}\n\nfunction GenerativeUiToolOutputPanel({ part }: { part: GenerativeUiToolPart }) {\n  const [isOpen, setIsOpen] = useState(false);\n\n  return (\n    <Tool\n      className=\"bg-muted/20\"\n      onOpenChange={(nextOpen) => setIsOpen(nextOpen)}\n      open={isOpen}\n    >\n      <ToolHeader\n        state={part.state}\n        title={getGenerativeUiToolOutputTitle(part.type)}\n        type={part.type}\n      />\n      <ToolContent>\n        {part.input ? <ToolInput input={part.input} /> : null}\n        <ToolOutput errorText={part.errorText} output={part.output} />\n        {part.output || part.errorText ? null : (\n          <div className=\"space-y-2\">\n            <h4 className=\"font-medium text-muted-foreground text-xs uppercase tracking-wide\">\n              Result\n            </h4>\n            <div className=\"rounded-md bg-muted/50 px-3 py-2 text-muted-foreground text-xs\">\n              Waiting for tool output.\n            </div>\n          </div>\n        )}\n      </ToolContent>\n    </Tool>\n  );\n}\n\nfunction renderGenerativeUiToolOutputPart(part: GenerativeUiToolPart) {\n  return (\n    <GenerativeUiToolOutputPanel\n      key={`${part.toolCallId}-tool-output`}\n      part={part}\n    />\n  );\n}\n\nfunction renderGenerativeUiToolPart(part: GenerativeUiToolPart) {\n  if (part.type === featureComparisonToolPartType) {\n    return <FeatureComparisonMatrix key={part.toolCallId} part={part} />;\n  }\n\n  if (part.type === planRecommendationToolPartType) {\n    return <PlanRecommendationCard key={part.toolCallId} part={part} />;\n  }\n\n  return null;\n}\n\nfunction GenerativeUiSources({\n  sources,\n}: {\n  sources: ReturnType<typeof projectGenerativeUiMessage>[\"sourceUrlParts\"];\n}) {\n  if (sources.length === 0) {\n    return null;\n  }\n\n  return (\n    <Sources>\n      <SourcesTrigger count={sources.length} />\n      <SourcesContent>\n        {sources.map((source) => (\n          <Source\n            href={source.url}\n            key={`${source.sourceId}-${source.url}`}\n            title={source.title ?? source.url}\n          />\n        ))}\n      </SourcesContent>\n    </Sources>\n  );\n}\n\nexport function GenerativeUiAssistantMessage({\n  isStreaming,\n  message,\n}: {\n  isStreaming: boolean;\n  message: UIMessage;\n}) {\n  const projection = projectGenerativeUiMessage(message);\n  const hasReasoningText = projection.reasoningText.length > 0;\n  const isReasoningStreaming =\n    isStreaming && message.parts.at(-1)?.type === \"reasoning\";\n  const shouldRenderReasoning = hasReasoningText || isReasoningStreaming;\n  const hasVisibleOutput =\n    shouldRenderReasoning ||\n    projection.text.length > 0 ||\n    projection.uiToolParts.length > 0 ||\n    projection.sourceUrlParts.length > 0 ||\n    projection.auxiliaryToolParts.length > 0;\n  const reasoningNode = shouldRenderReasoning ? (\n    <Reasoning className=\"w-full\" isStreaming={isReasoningStreaming}>\n      <ReasoningTrigger />\n      {hasReasoningText ? (\n        <ReasoningContent>{projection.reasoningText}</ReasoningContent>\n      ) : null}\n    </Reasoning>\n  ) : null;\n\n  return (\n    <div className=\"space-y-4\">\n      {reasoningNode}\n\n      {projection.uiToolParts.map(renderGenerativeUiToolOutputPart)}\n\n      {projection.text ? (\n        <MessageResponse>{projection.text}</MessageResponse>\n      ) : null}\n\n      {projection.uiToolParts.map(renderGenerativeUiToolPart)}\n\n      <GenerativeUiSources sources={projection.sourceUrlParts} />\n\n      {projection.auxiliaryToolParts.map((part) => (\n        <Tool className=\"bg-muted/20\" key={part.toolCallId}>\n          {part.type === \"dynamic-tool\" ? (\n            <ToolHeader\n              state={part.state}\n              title={part.toolName}\n              toolName={part.toolName}\n              type={part.type}\n            />\n          ) : (\n            <ToolHeader\n              state={part.state}\n              title={getToolDisplayTitle(part.type)}\n              type={part.type}\n            />\n          )}\n          <ToolContent>\n            {part.input ? <ToolInput input={part.input} /> : null}\n            <ToolOutput errorText={part.errorText} output={part.output} />\n          </ToolContent>\n        </Tool>\n      ))}\n\n      {hasVisibleOutput ? null : (\n        <p className=\"text-muted-foreground text-sm\">\n          {isStreaming ? \"Thinking...\" : \"Waiting for visible output.\"}\n        </p>\n      )}\n    </div>\n  );\n}\n\nfunction GenerativeUiMessage({\n  isStreaming,\n  message,\n}: {\n  isStreaming: boolean;\n  message: UIMessage;\n}) {\n  const projection = projectGenerativeUiMessage(message);\n  let messageBody: ReactNode;\n\n  if (message.role === \"assistant\") {\n    messageBody = (\n      <GenerativeUiAssistantMessage\n        isStreaming={isStreaming}\n        message={message}\n      />\n    );\n  } else if (projection.text) {\n    messageBody = <MessageResponse>{projection.text}</MessageResponse>;\n  } else {\n    messageBody = (\n      <p className=\"text-muted-foreground text-sm\">\n        Waiting for visible output.\n      </p>\n    );\n  }\n\n  return (\n    <Message from={message.role} key={message.id}>\n      <MessageContent\n        className={\n          message.role === \"assistant\"\n            ? \"w-full max-w-4xl overflow-visible\"\n            : \"max-w-2xl\"\n        }\n      >\n        {messageBody}\n      </MessageContent>\n    </Message>\n  );\n}\n\nexport interface GenerativeUiWorkspaceProps {\n  chatModel: string;\n  isChatAvailable: boolean;\n  nodeVersion: string;\n  setupMessage: string | null;\n}\n\nexport function GenerativeUiWorkspace({\n  chatModel,\n  isChatAvailable,\n  nodeVersion,\n  setupMessage,\n}: GenerativeUiWorkspaceProps) {\n  const {\n    clearError,\n    error,\n    hasMessages,\n    isBusy,\n    messages,\n    regenerate,\n    sendMessage,\n    status,\n    stop,\n  } = useGenerativeUiChat();\n  const retryConversationError = useConversationErrorRetry({\n    clearError,\n    regenerate,\n  });\n  const latestAssistantMessage = getLatestAssistantMessage(messages);\n\n  return (\n    <div className=\"grid min-h-[70svh] min-w-0 gap-4 lg:h-full lg:min-h-0 lg:grid-cols-[minmax(0,1fr)_19rem]\">\n      <section className=\"flex min-h-[70svh] min-w-0 flex-col border border-foreground/10 bg-background lg:h-full lg:min-h-0\">\n        {isChatAvailable ? null : (\n          <div className=\"border-foreground/10 border-b px-4 py-3 text-muted-foreground text-xs/relaxed\">\n            {setupMessage}\n          </div>\n        )}\n\n        <Conversation className=\"min-h-0\">\n          <ConversationContent className=\"mx-auto flex w-full max-w-4xl flex-1 gap-6 px-4 py-6\">\n            {hasMessages || error ? (\n              <>\n                {messages.map((message) => (\n                  <GenerativeUiMessage\n                    isStreaming={\n                      status === \"streaming\" &&\n                      message.id === latestAssistantMessage?.id\n                    }\n                    key={message.id}\n                    message={message}\n                  />\n                ))}\n                {error ? (\n                  <ConversationErrorMessage\n                    error={error}\n                    isRetryDisabled={isBusy || !isChatAvailable}\n                    onRetry={retryConversationError}\n                  />\n                ) : null}\n              </>\n            ) : (\n              <ConversationEmptyState\n                description=\"Ask for a comparison or a recommendation. Current questions can use search before the visual answer is generated.\"\n                icon={<SparklesIcon className=\"size-5\" />}\n                title=\"Generative UI workspace is ready\"\n              />\n            )}\n          </ConversationContent>\n          <ConversationScrollButton />\n        </Conversation>\n\n        <div className=\"border-foreground/10 border-t px-4 py-4\">\n          <div className=\"mx-auto w-full max-w-4xl\">\n            <PromptInput onSubmit={({ text }) => sendMessage({ text })}>\n              <PromptInputBody>\n                <PromptInputTextarea\n                  disabled={!isChatAvailable || isBusy}\n                  placeholder=\"Ask for a comparison, recommendation, or current research-backed decision.\"\n                />\n              </PromptInputBody>\n              <PromptInputFooter className=\"flex items-center justify-between gap-3 border-foreground/10 border-t px-3 py-3\">\n                <div className=\"flex flex-wrap items-center gap-2\">\n                  <Badge variant=\"outline\">AI SDK 6</Badge>\n                  <Badge variant=\"outline\">Generative UI</Badge>\n                  <Badge variant=\"outline\">{chatModel}</Badge>\n                </div>\n                <div className=\"flex items-center gap-2\">\n                  {isBusy ? (\n                    <Button\n                      onClick={stop}\n                      size=\"sm\"\n                      type=\"button\"\n                      variant=\"outline\"\n                    >\n                      <SquareIcon className=\"size-3.5\" />\n                      Stop\n                    </Button>\n                  ) : null}\n                  {hasMessages ? (\n                    <Button\n                      onClick={() => regenerate()}\n                      size=\"sm\"\n                      type=\"button\"\n                      variant=\"outline\"\n                    >\n                      <RefreshCwIcon className=\"size-3.5\" />\n                      Retry\n                    </Button>\n                  ) : null}\n                  <PromptInputSubmit\n                    disabled={!isChatAvailable}\n                    status={status}\n                  />\n                </div>\n              </PromptInputFooter>\n            </PromptInput>\n\n            {hasMessages ? null : (\n              <div className=\"mt-3 grid gap-2 sm:grid-cols-2\">\n                {generativeUiSamplePrompts.map((prompt) => (\n                  <Button\n                    className=\"h-auto justify-start whitespace-normal py-2 text-left\"\n                    disabled={!isChatAvailable || isBusy}\n                    key={prompt}\n                    onClick={() => sendMessage({ text: prompt })}\n                    size=\"sm\"\n                    type=\"button\"\n                    variant=\"outline\"\n                  >\n                    <BotIcon className=\"size-3.5 shrink-0\" />\n                    {prompt}\n                  </Button>\n                ))}\n              </div>\n            )}\n          </div>\n        </div>\n      </section>\n\n      <aside className=\"min-w-0 border border-foreground/10 bg-background p-4 lg:min-h-0 lg:overflow-y-auto\">\n        <div className=\"space-y-4\">\n          <div>\n            <p className=\"text-[11px] text-muted-foreground uppercase tracking-[0.2em]\">\n              Runtime\n            </p>\n            <p className=\"mt-1 font-medium text-sm\">{nodeVersion}</p>\n          </div>\n          <div>\n            <p className=\"text-[11px] text-muted-foreground uppercase tracking-[0.2em]\">\n              Tools\n            </p>\n            <div className=\"mt-2 flex flex-wrap gap-2\">\n              <Badge variant=\"outline\">showFeatureComparison</Badge>\n              <Badge variant=\"outline\">showPlanRecommendation</Badge>\n              <Badge variant=\"outline\">\n                <SearchIcon className=\"size-3\" />\n                web_search\n              </Badge>\n            </div>\n          </div>\n          <Separator />\n          <div>\n            <p className=\"text-[11px] text-muted-foreground uppercase tracking-[0.2em]\">\n              Output\n            </p>\n            <p className=\"mt-1 text-muted-foreground text-sm\">\n              Comparison matrix or recommendation card, selected by the model\n              during the chat run.\n            </p>\n          </div>\n        </div>\n      </aside>\n    </div>\n  );\n}\n",
      "type": "registry:component",
      "target": "@components/generative-ui/generative-ui-workspace.tsx"
    },
    {
      "path": "registry/generative-ui/components/generative-ui/feature-comparison-matrix.tsx",
      "content": "\"use client\";\n\nimport { Badge } from \"@/components/ui/badge\";\nimport {\n  Table,\n  TableBody,\n  TableCell,\n  TableHead,\n  TableHeader,\n  TableRow,\n} from \"@/components/ui/table\";\nimport { cn } from \"@/lib/utils\";\nimport { Table2Icon } from \"lucide-react\";\n\nimport type {\n  FeatureComparisonInput,\n  FeatureComparisonOutput,\n} from \"@/lib/generative-ui/ui-contract\";\nimport {\n  featureComparisonToolPartType,\n  type GenerativeUiToolPart,\n} from \"./message-parts\";\nimport {\n  getGeneratedString,\n  isRecord,\n  SkeletonLine,\n} from \"./partial-rendering\";\n\ninterface FeatureComparisonMatrixProps {\n  part: GenerativeUiToolPart;\n}\n\ntype PartialComparisonCriterion = Partial<\n  FeatureComparisonInput[\"criteria\"][number]\n> & {\n  scores?: Partial<\n    FeatureComparisonInput[\"criteria\"][number][\"scores\"][number]\n  >[];\n};\n\ntype PartialFeatureComparison = Partial<FeatureComparisonInput> & {\n  criteria?: PartialComparisonCriterion[];\n  options?: Partial<FeatureComparisonInput[\"options\"][number]>[];\n};\n\ntype ComparisonRenderState = \"final\" | \"streaming\" | \"validating\";\n\nconst ratingLabels: Record<string, string> = {\n  best: \"Best\",\n  mixed: \"Mixed\",\n  strong: \"Strong\",\n  weak: \"Weak\",\n};\n\nconst ratingClassNames: Record<string, string> = {\n  best: \"border-emerald-500/30 bg-emerald-500/10 text-emerald-700 dark:text-emerald-300\",\n  mixed:\n    \"border-amber-500/30 bg-amber-500/10 text-amber-700 dark:text-amber-300\",\n  strong: \"border-sky-500/30 bg-sky-500/10 text-sky-700 dark:text-sky-300\",\n  weak: \"border-rose-500/30 bg-rose-500/10 text-rose-700 dark:text-rose-300\",\n};\n\nfunction isFeatureComparisonOutput(\n  value: unknown\n): value is FeatureComparisonOutput {\n  if (!value || typeof value !== \"object\") {\n    return false;\n  }\n\n  const output = value as FeatureComparisonOutput;\n\n  return (\n    output.kind === \"feature-comparison\" &&\n    typeof output.subject === \"string\" &&\n    typeof output.summary === \"string\" &&\n    Array.isArray(output.options) &&\n    Array.isArray(output.criteria)\n  );\n}\n\nfunction renderPendingComparison() {\n  return (\n    <section className=\"not-prose overflow-hidden rounded-md border border-foreground/10 bg-muted/20\">\n      <div className=\"flex items-center justify-between gap-3 border-foreground/10 border-b px-4 py-3\">\n        <div className=\"flex items-center gap-2\">\n          <Table2Icon className=\"size-4 text-muted-foreground\" />\n          <span className=\"font-medium text-sm\">Preparing comparison</span>\n        </div>\n        <Badge variant=\"outline\">Working</Badge>\n      </div>\n      <div className=\"space-y-2 p-4\">\n        <div className=\"h-3 w-2/3 rounded bg-muted\" />\n        <div className=\"h-16 rounded bg-muted/70\" />\n      </div>\n    </section>\n  );\n}\n\nfunction renderComparisonError() {\n  return (\n    <section className=\"not-prose rounded-md border border-destructive/30 bg-destructive/5 p-4 text-destructive text-sm\">\n      Comparison output could not be finalized.\n    </section>\n  );\n}\n\nfunction getFeatureComparisonDraft(part: GenerativeUiToolPart) {\n  if (part.state === \"output-available\") {\n    return {\n      data: part.output,\n      renderState: \"final\" as const,\n    };\n  }\n\n  if (\n    (part.state === \"input-streaming\" || part.state === \"input-available\") &&\n    isRecord(part.input)\n  ) {\n    return {\n      data: part.input as PartialFeatureComparison,\n      renderState:\n        part.state === \"input-available\"\n          ? (\"validating\" as const)\n          : (\"streaming\" as const),\n    };\n  }\n\n  return;\n}\n\nfunction getBadgeLabel(renderState: ComparisonRenderState) {\n  if (renderState === \"streaming\") {\n    return \"Generating\";\n  }\n\n  if (renderState === \"validating\") {\n    return \"Validating\";\n  }\n\n  return \"Comparison generated\";\n}\n\nfunction getRatingLabel(rating: unknown) {\n  const ratingValue = getGeneratedString(rating);\n\n  return ratingValue ? ratingLabels[ratingValue] : undefined;\n}\n\nfunction renderScoreCell(\n  score: Partial<\n    FeatureComparisonInput[\"criteria\"][number][\"scores\"][number]\n  > | null,\n  renderState: ComparisonRenderState\n) {\n  if (!score) {\n    return renderState === \"final\" ? (\n      <span className=\"text-muted-foreground\">No score</span>\n    ) : (\n      <SkeletonLine className=\"w-3/4\" />\n    );\n  }\n\n  const rating = getGeneratedString(score.rating);\n  const ratingLabel = getRatingLabel(score.rating);\n  const summary = getGeneratedString(score.summary);\n\n  return (\n    <div className=\"space-y-2\">\n      {ratingLabel ? (\n        <Badge\n          className={cn(\"rounded-full\", rating && ratingClassNames[rating])}\n          variant=\"outline\"\n        >\n          {ratingLabel}\n        </Badge>\n      ) : (\n        <SkeletonLine className=\"w-20\" />\n      )}\n      <div className=\"text-muted-foreground\">\n        {summary ?? <SkeletonLine className=\"w-4/5\" />}\n      </div>\n    </div>\n  );\n}\n\nfunction renderComparisonContent(\n  output: PartialFeatureComparison,\n  renderState: ComparisonRenderState\n) {\n  const subject = getGeneratedString(output.subject);\n  const summary = getGeneratedString(output.summary);\n  const options = Array.isArray(output.options) ? output.options : [];\n  const criteria = Array.isArray(output.criteria) ? output.criteria : [];\n  const visibleOptions = options.length > 0 ? options : [undefined];\n  const visibleCriteria = criteria.length > 0 ? criteria : [undefined];\n\n  return (\n    <section className=\"not-prose overflow-hidden rounded-md border border-foreground/10 bg-background\">\n      <div className=\"space-y-2 border-foreground/10 border-b px-4 py-3\">\n        <div className=\"flex min-w-0 items-center justify-between gap-3\">\n          <div className=\"flex min-w-0 items-center gap-2\">\n            <Table2Icon className=\"size-4 shrink-0 text-muted-foreground\" />\n            <h3 className=\"min-w-0 flex-1 truncate font-medium text-sm\">\n              {subject ?? <SkeletonLine className=\"w-48 max-w-full\" />}\n            </h3>\n          </div>\n          <Badge variant=\"outline\">{getBadgeLabel(renderState)}</Badge>\n        </div>\n        <div className=\"text-muted-foreground text-sm\">\n          {summary ?? <SkeletonLine className=\"w-2/3\" />}\n        </div>\n      </div>\n\n      <div className=\"max-w-full overflow-x-auto\">\n        <Table className=\"min-w-[42rem]\">\n          <TableHeader>\n            <TableRow>\n              <TableHead className=\"w-44 whitespace-normal break-words\">\n                Criterion\n              </TableHead>\n              {visibleOptions.map((option, index) => {\n                const optionName = getGeneratedString(option?.name);\n                const optionSummary = getGeneratedString(option?.summary);\n\n                return (\n                  <TableHead\n                    className=\"whitespace-normal break-words\"\n                    key={optionName ?? `option-${index}`}\n                  >\n                    <div className=\"space-y-1\">\n                      <div className=\"font-medium text-foreground\">\n                        {optionName ?? <SkeletonLine className=\"w-28\" />}\n                      </div>\n                      <div className=\"text-muted-foreground text-xs\">\n                        {optionSummary ?? <SkeletonLine className=\"w-32\" />}\n                      </div>\n                    </div>\n                  </TableHead>\n                );\n              })}\n            </TableRow>\n          </TableHeader>\n          <TableBody>\n            {visibleCriteria.map((criterion, criterionIndex) => (\n              <TableRow\n                key={\n                  getGeneratedString(criterion?.label) ??\n                  `criterion-${criterionIndex}`\n                }\n              >\n                <TableCell className=\"whitespace-normal break-words align-top font-medium text-sm\">\n                  {getGeneratedString(criterion?.label) ?? (\n                    <SkeletonLine className=\"w-28\" />\n                  )}\n                </TableCell>\n                {visibleOptions.map((option, optionIndex) => {\n                  const optionName = getGeneratedString(option?.name);\n                  const scores = Array.isArray(criterion?.scores)\n                    ? criterion.scores\n                    : [];\n                  const score =\n                    scores.find((item) => item.option === optionName) ??\n                    scores[optionIndex] ??\n                    null;\n\n                  return (\n                    <TableCell\n                      className=\"min-w-48 whitespace-normal break-words align-top text-sm\"\n                      key={optionName ?? `score-${optionIndex}`}\n                    >\n                      {renderScoreCell(score, renderState)}\n                    </TableCell>\n                  );\n                })}\n              </TableRow>\n            ))}\n          </TableBody>\n        </Table>\n      </div>\n    </section>\n  );\n}\n\nexport function FeatureComparisonMatrix({\n  part,\n}: FeatureComparisonMatrixProps) {\n  if (part.type !== featureComparisonToolPartType) {\n    return null;\n  }\n\n  if (part.state === \"output-error\") {\n    return renderComparisonError();\n  }\n\n  const draft = getFeatureComparisonDraft(part);\n\n  if (!draft) {\n    return renderPendingComparison();\n  }\n\n  if (draft.renderState === \"final\") {\n    if (!isFeatureComparisonOutput(draft.data)) {\n      return renderComparisonError();\n    }\n\n    return renderComparisonContent(draft.data, draft.renderState);\n  }\n\n  return renderComparisonContent(draft.data, draft.renderState);\n}\n",
      "type": "registry:component",
      "target": "@components/generative-ui/feature-comparison-matrix.tsx"
    },
    {
      "path": "registry/generative-ui/components/generative-ui/plan-recommendation-card.tsx",
      "content": "\"use client\";\n\nimport { Badge } from \"@/components/ui/badge\";\nimport { CheckCircle2Icon, ListChecksIcon } from \"lucide-react\";\n\nimport type {\n  PlanRecommendationInput,\n  PlanRecommendationOutput,\n} from \"@/lib/generative-ui/ui-contract\";\nimport {\n  type GenerativeUiToolPart,\n  planRecommendationToolPartType,\n} from \"./message-parts\";\nimport {\n  getGeneratedString,\n  isRecord,\n  SkeletonLine,\n} from \"./partial-rendering\";\n\ninterface PlanRecommendationCardProps {\n  part: GenerativeUiToolPart;\n}\n\ntype PartialPlanRecommendation = Partial<PlanRecommendationInput> & {\n  alternatives?: Partial<PlanRecommendationInput[\"alternatives\"][number]>[];\n  rationale?: Partial<PlanRecommendationInput[\"rationale\"][number]>[];\n  recommendedOption?: Partial<PlanRecommendationInput[\"recommendedOption\"]>;\n  risks?: Partial<PlanRecommendationInput[\"risks\"][number]>[];\n};\n\ntype RecommendationRenderState = \"final\" | \"streaming\" | \"validating\";\n\nfunction isPlanRecommendationOutput(\n  value: unknown\n): value is PlanRecommendationOutput {\n  if (!value || typeof value !== \"object\") {\n    return false;\n  }\n\n  const output = value as PlanRecommendationOutput;\n\n  return (\n    output.kind === \"plan-recommendation\" &&\n    typeof output.decision === \"string\" &&\n    Array.isArray(output.alternatives) &&\n    Array.isArray(output.nextSteps) &&\n    Array.isArray(output.rationale) &&\n    Array.isArray(output.risks) &&\n    typeof output.recommendedOption?.name === \"string\"\n  );\n}\n\nfunction renderPendingRecommendation() {\n  return (\n    <section className=\"not-prose overflow-hidden rounded-md border border-foreground/10 bg-muted/20\">\n      <div className=\"flex items-center justify-between gap-3 border-foreground/10 border-b px-4 py-3\">\n        <div className=\"flex items-center gap-2\">\n          <ListChecksIcon className=\"size-4 text-muted-foreground\" />\n          <span className=\"font-medium text-sm\">Preparing recommendation</span>\n        </div>\n        <Badge variant=\"outline\">Working</Badge>\n      </div>\n      <div className=\"space-y-2 p-4\">\n        <div className=\"h-3 w-3/4 rounded bg-muted\" />\n        <div className=\"h-20 rounded bg-muted/70\" />\n      </div>\n    </section>\n  );\n}\n\nfunction renderRecommendationError() {\n  return (\n    <section className=\"not-prose rounded-md border border-destructive/30 bg-destructive/5 p-4 text-destructive text-sm\">\n      Recommendation output could not be finalized.\n    </section>\n  );\n}\n\nfunction getPlanRecommendationDraft(part: GenerativeUiToolPart) {\n  if (part.state === \"output-available\") {\n    return {\n      data: part.output,\n      renderState: \"final\" as const,\n    };\n  }\n\n  if (\n    (part.state === \"input-streaming\" || part.state === \"input-available\") &&\n    isRecord(part.input)\n  ) {\n    return {\n      data: part.input as PartialPlanRecommendation,\n      renderState:\n        part.state === \"input-available\"\n          ? (\"validating\" as const)\n          : (\"streaming\" as const),\n    };\n  }\n\n  return;\n}\n\nfunction getBadgeLabel(renderState: RecommendationRenderState) {\n  if (renderState === \"streaming\") {\n    return \"Generating\";\n  }\n\n  if (renderState === \"validating\") {\n    return \"Validating\";\n  }\n\n  return \"Recommendation generated\";\n}\n\nfunction renderRiskMitigation(\n  mitigation: unknown,\n  renderState: RecommendationRenderState\n) {\n  const generatedMitigation = getGeneratedString(mitigation);\n\n  if (generatedMitigation) {\n    return (\n      <p className=\"mt-1 text-muted-foreground text-sm\">\n        {generatedMitigation}\n      </p>\n    );\n  }\n\n  if (renderState === \"final\") {\n    return null;\n  }\n\n  return (\n    <div className=\"mt-1\">\n      <SkeletonLine className=\"w-3/4\" />\n    </div>\n  );\n}\n\nfunction renderRecommendationContent(\n  output: PartialPlanRecommendation,\n  renderState: RecommendationRenderState\n) {\n  const decision = getGeneratedString(output.decision);\n  const recommendedName = getGeneratedString(output.recommendedOption?.name);\n  const recommendedSummary = getGeneratedString(\n    output.recommendedOption?.summary\n  );\n  const rationale = Array.isArray(output.rationale) ? output.rationale : [];\n  const alternatives = Array.isArray(output.alternatives)\n    ? output.alternatives\n    : [];\n  const risks = Array.isArray(output.risks) ? output.risks : [];\n  const nextSteps = Array.isArray(output.nextSteps) ? output.nextSteps : [];\n\n  return (\n    <section className=\"not-prose overflow-hidden rounded-md border border-foreground/10 bg-background\">\n      <div className=\"space-y-3 border-foreground/10 border-b px-4 py-4\">\n        <div className=\"flex min-w-0 items-center justify-between gap-3\">\n          <div className=\"flex min-w-0 items-center gap-2\">\n            <ListChecksIcon className=\"size-4 shrink-0 text-muted-foreground\" />\n            <h3 className=\"min-w-0 flex-1 truncate font-medium text-sm\">\n              {decision ?? <SkeletonLine className=\"w-48 max-w-full\" />}\n            </h3>\n          </div>\n          <Badge variant=\"outline\">{getBadgeLabel(renderState)}</Badge>\n        </div>\n        <div className=\"rounded-md border border-emerald-500/20 bg-emerald-500/5 p-3\">\n          <div className=\"flex items-start gap-2\">\n            <CheckCircle2Icon className=\"mt-0.5 size-4 shrink-0 text-emerald-600\" />\n            <div className=\"w-full space-y-1\">\n              <p className=\"font-medium\">\n                {recommendedName ?? <SkeletonLine className=\"w-40\" />}\n              </p>\n              <div className=\"text-muted-foreground text-sm\">\n                {recommendedSummary ?? <SkeletonLine className=\"w-2/3\" />}\n              </div>\n            </div>\n          </div>\n        </div>\n      </div>\n\n      <div className=\"grid gap-4 p-4 md:grid-cols-2\">\n        <div className=\"space-y-3\">\n          <p className=\"text-muted-foreground text-xs uppercase tracking-[0.16em]\">\n            Rationale\n          </p>\n          <div className=\"space-y-2\">\n            {rationale.length > 0 ? (\n              rationale.map((item, index) => (\n                <div\n                  className=\"rounded-md border border-foreground/10 p-3\"\n                  key={getGeneratedString(item.label) ?? `rationale-${index}`}\n                >\n                  <p className=\"font-medium text-sm\">\n                    {getGeneratedString(item.label) ?? (\n                      <SkeletonLine className=\"w-28\" />\n                    )}\n                  </p>\n                  <div className=\"mt-1 text-muted-foreground text-sm\">\n                    {getGeneratedString(item.detail) ?? (\n                      <SkeletonLine className=\"w-4/5\" />\n                    )}\n                  </div>\n                </div>\n              ))\n            ) : (\n              <SkeletonLine className=\"h-16 w-full\" />\n            )}\n          </div>\n        </div>\n\n        <div className=\"space-y-3\">\n          <p className=\"text-muted-foreground text-xs uppercase tracking-[0.16em]\">\n            Alternatives\n          </p>\n          <div className=\"space-y-2\">\n            {alternatives.length > 0 ? (\n              alternatives.map((alternative, index) => (\n                <div\n                  className=\"rounded-md border border-foreground/10 p-3\"\n                  key={\n                    getGeneratedString(alternative.name) ??\n                    `alternative-${index}`\n                  }\n                >\n                  <p className=\"font-medium text-sm\">\n                    {getGeneratedString(alternative.name) ?? (\n                      <SkeletonLine className=\"w-28\" />\n                    )}\n                  </p>\n                  <div className=\"mt-1 text-muted-foreground text-sm\">\n                    {getGeneratedString(alternative.tradeoff) ?? (\n                      <SkeletonLine className=\"w-4/5\" />\n                    )}\n                  </div>\n                </div>\n              ))\n            ) : (\n              <SkeletonLine className=\"h-16 w-full\" />\n            )}\n          </div>\n        </div>\n\n        {risks.length > 0 ? (\n          <div className=\"space-y-3\">\n            <p className=\"text-muted-foreground text-xs uppercase tracking-[0.16em]\">\n              Risks\n            </p>\n            <div className=\"space-y-2\">\n              {risks.map((risk, index) => (\n                <div\n                  className=\"rounded-md border border-foreground/10 p-3\"\n                  key={getGeneratedString(risk.risk) ?? `risk-${index}`}\n                >\n                  <p className=\"font-medium text-sm\">\n                    {getGeneratedString(risk.risk) ?? (\n                      <SkeletonLine className=\"w-32\" />\n                    )}\n                  </p>\n                  {renderRiskMitigation(risk.mitigation, renderState)}\n                </div>\n              ))}\n            </div>\n          </div>\n        ) : null}\n\n        <div className=\"space-y-3\">\n          <p className=\"text-muted-foreground text-xs uppercase tracking-[0.16em]\">\n            Next steps\n          </p>\n          <ol className=\"space-y-2\">\n            {nextSteps.length > 0 ? (\n              nextSteps.map((step, index) => (\n                <li className=\"flex gap-2 text-sm\" key={step}>\n                  <span className=\"flex size-5 shrink-0 items-center justify-center rounded-full border border-foreground/10 text-[11px]\">\n                    {index + 1}\n                  </span>\n                  <span className=\"text-muted-foreground\">{step}</span>\n                </li>\n              ))\n            ) : (\n              <li className=\"flex gap-2 text-sm\">\n                <span className=\"flex size-5 shrink-0 items-center justify-center rounded-full border border-foreground/10 text-[11px]\">\n                  1\n                </span>\n                <span className=\"flex-1 pt-1\">\n                  <SkeletonLine className=\"w-3/4\" />\n                </span>\n              </li>\n            )}\n          </ol>\n        </div>\n      </div>\n    </section>\n  );\n}\n\nexport function PlanRecommendationCard({ part }: PlanRecommendationCardProps) {\n  if (part.type !== planRecommendationToolPartType) {\n    return null;\n  }\n\n  if (part.state === \"output-error\") {\n    return renderRecommendationError();\n  }\n\n  const draft = getPlanRecommendationDraft(part);\n\n  if (!draft) {\n    return renderPendingRecommendation();\n  }\n\n  if (draft.renderState === \"final\") {\n    if (!isPlanRecommendationOutput(draft.data)) {\n      return renderRecommendationError();\n    }\n\n    return renderRecommendationContent(draft.data, draft.renderState);\n  }\n\n  return renderRecommendationContent(draft.data, draft.renderState);\n}\n",
      "type": "registry:component",
      "target": "@components/generative-ui/plan-recommendation-card.tsx"
    },
    {
      "path": "registry/generative-ui/components/generative-ui/message-parts.ts",
      "content": "import type { ToolPart } from \"@/components/ai-elements/tool\";\nimport {\n  isReasoningUIPart,\n  isToolUIPart,\n  type SourceUrlUIPart,\n  type UIMessage,\n} from \"ai\";\n\nexport const featureComparisonToolPartType = \"tool-showFeatureComparison\";\nexport const planRecommendationToolPartType = \"tool-showPlanRecommendation\";\nexport const webSearchToolPartType = \"tool-web_search\";\n\nexport type GenerativeUiSource = Pick<\n  SourceUrlUIPart,\n  \"sourceId\" | \"title\" | \"url\"\n>;\n\nexport type GenerativeUiToolPart = ToolPart & {\n  type:\n    | typeof featureComparisonToolPartType\n    | typeof planRecommendationToolPartType;\n};\n\nexport interface GenerativeUiMessageProjection {\n  auxiliaryToolParts: ToolPart[];\n  hasReasoningSignal: boolean;\n  reasoningText: string;\n  sourceUrlParts: GenerativeUiSource[];\n  text: string;\n  uiToolParts: GenerativeUiToolPart[];\n}\n\nexport function isGenerativeUiToolPart(\n  part: UIMessage[\"parts\"][number]\n): part is GenerativeUiToolPart {\n  return (\n    isToolUIPart(part) &&\n    (part.type === featureComparisonToolPartType ||\n      part.type === planRecommendationToolPartType)\n  );\n}\n\nfunction isSourceUrlPart(\n  part: UIMessage[\"parts\"][number]\n): part is SourceUrlUIPart {\n  return (\n    part.type === \"source-url\" &&\n    \"sourceId\" in part &&\n    typeof part.sourceId === \"string\" &&\n    part.sourceId.trim().length > 0 &&\n    \"url\" in part &&\n    typeof part.url === \"string\" &&\n    part.url.trim().length > 0\n  );\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n  return typeof value === \"object\" && value !== null;\n}\n\nfunction isWebSearchUrlSource(\n  value: unknown\n): value is { type: \"url\"; url: string } {\n  return (\n    isRecord(value) &&\n    value.type === \"url\" &&\n    typeof value.url === \"string\" &&\n    value.url.trim().length > 0\n  );\n}\n\nfunction addSource(\n  sources: Map<string, GenerativeUiSource>,\n  source: GenerativeUiSource\n) {\n  const sourceKey = source.url.trim();\n\n  if (!sources.has(sourceKey)) {\n    sources.set(sourceKey, {\n      ...source,\n      url: sourceKey,\n    });\n  }\n}\n\nfunction addWebSearchOutputSources(\n  sources: Map<string, GenerativeUiSource>,\n  part: ToolPart\n) {\n  if (part.type !== webSearchToolPartType || !isRecord(part.output)) {\n    return;\n  }\n\n  const rawSources = part.output.sources;\n\n  if (!Array.isArray(rawSources)) {\n    return;\n  }\n\n  rawSources.filter(isWebSearchUrlSource).forEach((source, index) => {\n    addSource(sources, {\n      sourceId: `web_search_${index}_${source.url}`,\n      url: source.url,\n    });\n  });\n}\n\nfunction getSourceUrlParts(message: UIMessage) {\n  const sources = new Map<string, GenerativeUiSource>();\n\n  for (const part of message.parts) {\n    if (!isSourceUrlPart(part)) {\n      continue;\n    }\n\n    addSource(sources, part);\n  }\n\n  for (const part of message.parts) {\n    if (!isToolUIPart(part)) {\n      continue;\n    }\n\n    addWebSearchOutputSources(sources, part as ToolPart);\n  }\n\n  return [...sources.values()];\n}\n\nexport function projectGenerativeUiMessage(\n  message: UIMessage\n): GenerativeUiMessageProjection {\n  const text = message.parts\n    .filter((part) => part.type === \"text\")\n    .map((part) => part.text)\n    .join(\"\\n\");\n  const reasoningParts = message.parts.filter(isReasoningUIPart);\n  const joinedReasoningText = reasoningParts\n    .map((part) => part.text)\n    .join(\"\\n\\n\")\n    .trim();\n  const toolParts = message.parts.filter(isToolUIPart) as ToolPart[];\n  const uiToolParts = message.parts.filter(isGenerativeUiToolPart);\n\n  return {\n    auxiliaryToolParts: toolParts.filter(\n      (part) => !isGenerativeUiToolPart(part)\n    ),\n    hasReasoningSignal: reasoningParts.length > 0,\n    reasoningText: joinedReasoningText,\n    sourceUrlParts: getSourceUrlParts(message),\n    text,\n    uiToolParts,\n  };\n}\n",
      "type": "registry:component",
      "target": "@components/generative-ui/message-parts.ts"
    },
    {
      "path": "registry/generative-ui/components/generative-ui/partial-rendering.tsx",
      "content": "export function isRecord(value: unknown): value is Record<string, unknown> {\n  return typeof value === \"object\" && value !== null;\n}\n\nexport function getGeneratedString(value: unknown) {\n  return typeof value === \"string\" && value.trim().length > 0\n    ? value\n    : undefined;\n}\n\nexport function SkeletonLine({ className = \"w-full\" }: { className?: string }) {\n  return (\n    <span\n      aria-hidden=\"true\"\n      className={`block h-3 rounded bg-muted ${className}`}\n    />\n  );\n}\n",
      "type": "registry:component",
      "target": "@components/generative-ui/partial-rendering.tsx"
    },
    {
      "path": "registry/generative-ui/components/generative-ui/use-generative-ui-chat.ts",
      "content": "\"use client\";\n\nimport { useDemoChat } from \"@/components/demo-chat/use-demo-chat\";\n\nexport function useGenerativeUiChat() {\n  return useDemoChat({ api: \"/api/demos/generative-ui\" });\n}\n",
      "type": "registry:component",
      "target": "@components/generative-ui/use-generative-ui-chat.ts"
    },
    {
      "path": "registry/generative-ui/components/demo-chat/use-demo-chat.ts",
      "content": "\"use client\";\n\nimport { Chat, useChat } from \"@ai-sdk/react\";\nimport { type ChatStatus, DefaultChatTransport, type UIMessage } from \"ai\";\nimport { useState } from \"react\";\n\ninterface CreateDemoChatOptions {\n  api: string;\n}\n\ninterface UseDemoChatWithApiOptions extends CreateDemoChatOptions {\n  createChat?: never;\n}\n\ninterface UseDemoChatWithFactoryOptions<TMessage extends UIMessage> {\n  api?: never;\n  createChat: () => Chat<TMessage>;\n}\n\ntype UseDemoChatOptions<TMessage extends UIMessage> =\n  | UseDemoChatWithApiOptions\n  | UseDemoChatWithFactoryOptions<TMessage>;\n\nexport function createDemoChat<TMessage extends UIMessage = UIMessage>({\n  api,\n}: CreateDemoChatOptions) {\n  return new Chat<TMessage>({\n    transport: new DefaultChatTransport({\n      api,\n    }),\n  });\n}\n\nexport function isDemoChatBusyStatus(status: ChatStatus) {\n  return status === \"submitted\" || status === \"streaming\";\n}\n\nexport function useDemoChat<TMessage extends UIMessage = UIMessage>(\n  options: UseDemoChatOptions<TMessage>\n) {\n  const [chat] = useState(() => {\n    if (options.createChat) {\n      return options.createChat();\n    }\n\n    return createDemoChat<TMessage>({ api: options.api });\n  });\n  const controller = useChat({ chat });\n  const hasMessages = controller.messages.length > 0;\n  const isBusy = isDemoChatBusyStatus(controller.status);\n\n  return {\n    ...controller,\n    chat,\n    hasMessages,\n    isBusy,\n  };\n}\n",
      "type": "registry:component",
      "target": "@components/demo-chat/use-demo-chat.ts"
    },
    {
      "path": "registry/generative-ui/components/demo-chat/conversation-error-message.tsx",
      "content": "\"use client\";\n\nimport {\n  Message,\n  MessageContent,\n} from \"@/components/ai-elements/message\";\nimport { Button } from \"@/components/ui/button\";\nimport { cn } from \"@/lib/utils\";\nimport { RefreshCcwIcon } from \"lucide-react\";\nimport { useCallback } from \"react\";\n\ninterface ConversationErrorMessageProps {\n  className?: string;\n  error: Error | string;\n  isRetryDisabled?: boolean;\n  onRetry?: () => Promise<void> | void;\n  retryLabel?: string;\n  title?: string;\n}\n\ninterface UseConversationErrorRetryInput {\n  clearError: () => void;\n  regenerate: () => Promise<void>;\n}\n\nexport function useConversationErrorRetry({\n  clearError,\n  regenerate,\n}: UseConversationErrorRetryInput) {\n  return useCallback(async () => {\n    clearError();\n    await regenerate();\n  }, [clearError, regenerate]);\n}\n\nexport function ConversationErrorMessage({\n  className,\n  error,\n  isRetryDisabled = false,\n  retryLabel = \"Retry\",\n  onRetry,\n  title = \"Assistant response failed\",\n}: ConversationErrorMessageProps) {\n  const errorMessage = typeof error === \"string\" ? error : error.message;\n\n  return (\n    <Message from=\"assistant\">\n      <MessageContent\n        className={cn(\n          \"max-w-3xl border border-destructive/25 bg-destructive/5 px-4 py-3 text-destructive\",\n          className\n        )}\n      >\n        <div className=\"flex flex-col gap-3 sm:flex-row sm:items-start sm:justify-between\">\n          <div className=\"min-w-0\">\n            <p className=\"font-medium text-sm\">{title}</p>\n            <p className=\"mt-1 break-words text-xs/relaxed opacity-90\">\n              {errorMessage}\n            </p>\n          </div>\n          {onRetry ? (\n            <Button\n              className=\"self-start\"\n              disabled={isRetryDisabled}\n              onClick={() => {\n                void onRetry();\n              }}\n              size=\"sm\"\n              type=\"button\"\n              variant=\"destructive\"\n            >\n              <RefreshCcwIcon className=\"size-3.5\" />\n              {retryLabel}\n            </Button>\n          ) : null}\n        </div>\n      </MessageContent>\n    </Message>\n  );\n}\n",
      "type": "registry:component",
      "target": "@components/demo-chat/conversation-error-message.tsx"
    },
    {
      "path": "registry/generative-ui/components/demo-workspace-shell.tsx",
      "content": "import { Badge } from \"@/components/ui/badge\";\nimport { Card } from \"@/components/ui/card\";\nimport { cn } from \"@/lib/utils\";\nimport type { ReactNode } from \"react\";\n\nimport { DemoBreadcrumb } from \"@/components/demo-breadcrumb\";\n\ntype DemoWorkspaceHeaderFrame = \"header\" | \"card\";\n\ninterface DemoWorkspaceShellProps {\n  badges?: readonly ReactNode[];\n  breadcrumbClassName?: string;\n  breadcrumbTitle?: string;\n  children: ReactNode;\n  contentClassName?: string;\n  headerClassName?: string;\n  headerFrame?: DemoWorkspaceHeaderFrame;\n  maxWidthClassName?: string;\n  summary: ReactNode;\n  summaryClassName?: string;\n  title: string;\n  titleClassName?: string;\n  workspaceClassName?: string | null;\n}\n\nconst defaultHeaderClassName =\n  \"grid gap-4 border border-foreground/10 bg-background px-4 py-5 md:grid-cols-[minmax(0,1fr)_auto] md:items-end\";\n\nconst cardHeaderClassName =\n  \"grid gap-4 bg-background px-4 py-5 text-base text-foreground leading-normal md:grid-cols-[minmax(0,1fr)_auto] md:items-end\";\n\nexport function DemoWorkspaceShell({\n  badges = [],\n  breadcrumbClassName,\n  breadcrumbTitle,\n  children,\n  contentClassName,\n  headerClassName,\n  headerFrame = \"header\",\n  maxWidthClassName = \"max-w-7xl\",\n  summary,\n  summaryClassName = \"max-w-3xl\",\n  title,\n  titleClassName = \"max-w-3xl\",\n  workspaceClassName = \"lg:h-svh\",\n}: DemoWorkspaceShellProps) {\n  const headerContent = (\n    <>\n      <div className=\"space-y-2\">\n        <DemoBreadcrumb\n          className={breadcrumbClassName}\n          title={breadcrumbTitle ?? title}\n        />\n        <h1\n          className={cn(\"font-medium text-2xl tracking-tight\", titleClassName)}\n        >\n          {title}\n        </h1>\n        <p\n          className={cn(\n            \"text-muted-foreground text-sm/relaxed\",\n            summaryClassName\n          )}\n        >\n          {summary}\n        </p>\n      </div>\n\n      {badges.length > 0 ? (\n        <div className=\"flex flex-wrap items-center gap-2\">\n          {badges.map((badge, index) => (\n            <Badge key={String(index)} variant=\"outline\">\n              {badge}\n            </Badge>\n          ))}\n        </div>\n      ) : null}\n    </>\n  );\n\n  return (\n    <main className=\"min-h-svh bg-background text-foreground\">\n      <div\n        className={cn(\n          \"mx-auto flex w-full flex-col gap-6 px-4 py-6 md:px-6\",\n          maxWidthClassName,\n          contentClassName\n        )}\n      >\n        {headerFrame === \"card\" ? (\n          <Card className={cn(cardHeaderClassName, headerClassName)}>\n            {headerContent}\n          </Card>\n        ) : (\n          <header className={cn(defaultHeaderClassName, headerClassName)}>\n            {headerContent}\n          </header>\n        )}\n\n        {workspaceClassName ? (\n          <div className={workspaceClassName}>{children}</div>\n        ) : (\n          children\n        )}\n      </div>\n    </main>\n  );\n}\n",
      "type": "registry:component",
      "target": "@components/demo-workspace-shell.tsx"
    },
    {
      "path": "registry/generative-ui/components/demo-breadcrumb.tsx",
      "content": "import {\n  Breadcrumb,\n  BreadcrumbItem,\n  BreadcrumbLink,\n  BreadcrumbList,\n  BreadcrumbPage,\n  BreadcrumbSeparator,\n} from \"@/components/ui/breadcrumb\";\nimport { cn } from \"@/lib/utils\";\nimport { ArrowLeft } from \"lucide-react\";\n\ninterface DemoBreadcrumbProps {\n  className?: string;\n  title: string;\n}\n\nexport function DemoBreadcrumb({ className, title }: DemoBreadcrumbProps) {\n  return (\n    <Breadcrumb>\n      <BreadcrumbList\n        className={cn(\n          \"text-[11px] text-muted-foreground uppercase tracking-[0.2em]\",\n          className\n        )}\n      >\n        <BreadcrumbItem>\n          <BreadcrumbLink\n            aria-label=\"Back to demos\"\n            className=\"-ml-1 inline-flex items-center gap-1.5 text-muted-foreground transition-colors hover:text-foreground\"\n            href=\"/\"\n          >\n            <ArrowLeft aria-hidden=\"true\" className=\"size-3.5 shrink-0\" />\n            <span>Demo</span>\n          </BreadcrumbLink>\n        </BreadcrumbItem>\n        <BreadcrumbSeparator className=\"text-muted-foreground\">\n          /\n        </BreadcrumbSeparator>\n        <BreadcrumbItem>\n          <BreadcrumbPage className=\"font-normal text-muted-foreground\">\n            {title}\n          </BreadcrumbPage>\n        </BreadcrumbItem>\n      </BreadcrumbList>\n    </Breadcrumb>\n  );\n}\n",
      "type": "registry:component",
      "target": "@components/demo-breadcrumb.tsx"
    },
    {
      "path": "registry/generative-ui/components/ai-elements/prompt-input.tsx",
      "content": "\"use client\";\n\nimport { CornerDownLeftIcon, LoaderCircleIcon, SquareIcon } from \"lucide-react\";\nimport type {\n  ComponentProps,\n  FormEvent,\n  HTMLAttributes,\n  ReactNode,\n  TextareaHTMLAttributes,\n} from \"react\";\nimport {\n  createContext,\n  useCallback,\n  useContext,\n  useMemo,\n  useState,\n} from \"react\";\nimport { Button } from \"@/components/ui/button\";\nimport { Textarea } from \"@/components/ui/textarea\";\nimport { cn } from \"@/lib/utils\";\n\ntype PromptStatus = \"error\" | \"ready\" | \"streaming\" | \"submitted\";\n\ninterface PromptInputContextValue {\n  setText: (text: string) => void;\n  text: string;\n}\n\ninterface PromptInputMessage {\n  text: string;\n}\n\ninterface PromptInputProps\n  extends Omit<HTMLAttributes<HTMLFormElement>, \"onSubmit\"> {\n  children: ReactNode;\n  onSubmit: (\n    message: PromptInputMessage,\n    event: FormEvent<HTMLFormElement>\n  ) => void | Promise<void>;\n}\n\ninterface PromptInputSubmitProps\n  extends Omit<ComponentProps<typeof Button>, \"children\" | \"type\"> {\n  onStop?: () => void;\n  status?: PromptStatus;\n}\n\ntype PromptInputClickEvent = Parameters<\n  Exclude<ComponentProps<typeof Button>[\"onClick\"], undefined>\n>[0];\n\nconst PromptInputContext = createContext<PromptInputContextValue | null>(null);\n\nfunction usePromptInputContext() {\n  const context = useContext(PromptInputContext);\n\n  if (!context) {\n    throw new Error(\n      \"PromptInput components must be used inside <PromptInput>.\"\n    );\n  }\n\n  return context;\n}\n\nexport function PromptInput({\n  children,\n  className,\n  onSubmit,\n  ...props\n}: PromptInputProps) {\n  const [text, setText] = useState(\"\");\n\n  const contextValue = useMemo(\n    () => ({\n      setText,\n      text,\n    }),\n    [text]\n  );\n\n  const handleSubmit = useCallback(\n    async (event: FormEvent<HTMLFormElement>) => {\n      event.preventDefault();\n\n      const nextText = text.trim();\n      if (!nextText) {\n        return;\n      }\n\n      const result = onSubmit({ text: nextText }, event);\n\n      try {\n        await result;\n        setText(\"\");\n      } catch {\n        // Keep the text for retry after a failed submit.\n      }\n    },\n    [onSubmit, text]\n  );\n\n  return (\n    <PromptInputContext.Provider value={contextValue}>\n      <form\n        className={cn(\"w-full\", className)}\n        onSubmit={handleSubmit}\n        {...props}\n      >\n        {children}\n      </form>\n    </PromptInputContext.Provider>\n  );\n}\n\nexport function PromptInputBody({\n  className,\n  ...props\n}: HTMLAttributes<HTMLDivElement>) {\n  return <div className={cn(\"grid gap-3\", className)} {...props} />;\n}\n\nexport function PromptInputFooter({\n  className,\n  ...props\n}: HTMLAttributes<HTMLDivElement>) {\n  return <div className={cn(className)} {...props} />;\n}\n\nexport function PromptInputTextarea({\n  className,\n  disabled,\n  onChange,\n  ...props\n}: TextareaHTMLAttributes<HTMLTextAreaElement>) {\n  const context = usePromptInputContext();\n\n  return (\n    <Textarea\n      className={cn(\"min-h-16 resize-none\", className)}\n      disabled={disabled}\n      onChange={(event) => {\n        context.setText(event.currentTarget.value);\n        onChange?.(event);\n      }}\n      value={context.text}\n      {...props}\n    />\n  );\n}\n\nexport function PromptInputSubmit({\n  className,\n  disabled,\n  onClick,\n  onStop,\n  size = \"icon\",\n  status = \"ready\",\n  variant = \"default\",\n  ...props\n}: PromptInputSubmitProps) {\n  const { text } = usePromptInputContext();\n  const isBusy = status === \"submitted\" || status === \"streaming\";\n  const isDisabled = Boolean(disabled) || (!isBusy && text.trim().length === 0);\n\n  let icon = <CornerDownLeftIcon className=\"size-4\" />;\n\n  if (status === \"submitted\") {\n    icon = <LoaderCircleIcon className=\"size-4 animate-spin\" />;\n  } else if (status === \"streaming\") {\n    icon = <SquareIcon className=\"size-4\" />;\n  }\n\n  return (\n    <Button\n      aria-label={isBusy ? \"Stop\" : \"Submit\"}\n      className={cn(className)}\n      disabled={isDisabled}\n      onClick={(event: PromptInputClickEvent) => {\n        if (isBusy && onStop) {\n          event.preventDefault();\n          onStop();\n          return;\n        }\n\n        onClick?.(event);\n      }}\n      size={size}\n      type={isBusy && onStop ? \"button\" : \"submit\"}\n      variant={variant}\n      {...props}\n    >\n      {icon}\n    </Button>\n  );\n}\n",
      "type": "registry:component",
      "target": "@components/ai-elements/prompt-input.tsx"
    },
    {
      "path": "registry/generative-ui/lib/ai-gateway/contract.ts",
      "content": "import { createGateway } from \"ai\";\n\nexport const DEFAULT_GATEWAY_BASE_URL = \"https://ai-gateway.vercel.sh/v3/ai\";\nexport const MINIMUM_NODE_VERSION = \"22.13.0\";\nconst nodeVersionPattern = /^v?(\\d+)\\.(\\d+)\\.(\\d+)(?:[-+].*)?$/;\n\nexport type AiGatewayEnvRecord = Record<string, string | undefined>;\n\nexport interface ParsedNodeVersion {\n  major: number;\n  minor: number;\n  patch: number;\n}\n\nexport interface AiGatewayContractConfig {\n  apiKey: string;\n  baseURL: string;\n  chatModel: string;\n}\n\nexport interface AiGatewayResolvedEnv {\n  apiKey: string | undefined;\n  baseURL: string;\n  chatModel: string;\n}\n\nexport interface AiGatewaySetupConfig {\n  baseURL: string;\n  chatModel: string;\n}\n\nexport interface AiGatewayContractSetupState<\n  TConfig extends AiGatewaySetupConfig = AiGatewaySetupConfig,\n> {\n  config: TConfig;\n  isReady: boolean;\n  issues: string[];\n  nodeVersion: string;\n}\n\nexport interface AiGatewayContractOptions<\n  TConfig extends AiGatewaySetupConfig = AiGatewaySetupConfig,\n> {\n  buildConfig?: (\n    resolvedEnv: AiGatewayResolvedEnv,\n    env: AiGatewayEnvRecord\n  ) => TConfig;\n  defaultBaseURL?: string;\n  defaultChatModel: string;\n  getAdditionalIssues?: (\n    resolvedEnv: AiGatewayResolvedEnv,\n    env: AiGatewayEnvRecord\n  ) => string[];\n  missingApiKeyError: string;\n  missingApiKeyIssue?: string;\n}\n\nconst genericMissingApiKeyIssue =\n  \"AI_GATEWAY_API_KEY is missing. The demo can render, but chat requests will fail until it is configured.\";\n\nexport function parseNodeVersion(version: string): ParsedNodeVersion {\n  const match = nodeVersionPattern.exec(version);\n  const major = Number(match?.[1]);\n  const minor = Number(match?.[2]);\n  const patch = Number(match?.[3]);\n\n  if (![major, minor, patch].every(Number.isInteger)) {\n    throw new Error(`Unable to parse Node.js version: \"${version}\".`);\n  }\n\n  return { major, minor, patch };\n}\n\nexport function getNodeMajor(version: string): number {\n  return parseNodeVersion(version).major;\n}\n\nfunction compareNodeVersions(\n  left: ParsedNodeVersion,\n  right: ParsedNodeVersion\n): number {\n  if (left.major !== right.major) {\n    return left.major - right.major;\n  }\n\n  if (left.minor !== right.minor) {\n    return left.minor - right.minor;\n  }\n\n  return left.patch - right.patch;\n}\n\nexport function assertSupportedNodeRuntime(version = process.version): number {\n  const parsedVersion = parseNodeVersion(version);\n  const minimumVersion = parseNodeVersion(MINIMUM_NODE_VERSION);\n\n  if (compareNodeVersions(parsedVersion, minimumVersion) < 0) {\n    throw new Error(\n      `Node.js ${version} is unsupported. This demo workspace requires Node.js >=${MINIMUM_NODE_VERSION}.`\n    );\n  }\n\n  return parsedVersion.major;\n}\n\nexport function resolveAiGatewayContractEnv(\n  env: AiGatewayEnvRecord,\n  options: Pick<AiGatewayContractOptions, \"defaultBaseURL\" | \"defaultChatModel\">\n): AiGatewayResolvedEnv {\n  return {\n    apiKey: env.AI_GATEWAY_API_KEY,\n    baseURL:\n      env.AI_GATEWAY_BASE_URL ||\n      options.defaultBaseURL ||\n      DEFAULT_GATEWAY_BASE_URL,\n    chatModel: env.AI_GATEWAY_CHAT_MODEL || options.defaultChatModel,\n  };\n}\n\nexport function readAiGatewayContractConfig(\n  env: AiGatewayEnvRecord,\n  options: AiGatewayContractOptions\n): AiGatewayContractConfig {\n  assertSupportedNodeRuntime();\n  const resolvedEnv = resolveAiGatewayContractEnv(env, options);\n\n  if (!resolvedEnv.apiKey) {\n    throw new Error(options.missingApiKeyError);\n  }\n\n  return {\n    apiKey: resolvedEnv.apiKey,\n    baseURL: resolvedEnv.baseURL,\n    chatModel: resolvedEnv.chatModel,\n  };\n}\n\nexport function buildAiGatewayContractSetupState<\n  TConfig extends AiGatewaySetupConfig,\n>(\n  env: AiGatewayEnvRecord,\n  options: AiGatewayContractOptions<TConfig>\n): AiGatewayContractSetupState<TConfig> {\n  const issues: string[] = [];\n  const resolvedEnv = resolveAiGatewayContractEnv(env, options);\n\n  try {\n    assertSupportedNodeRuntime();\n  } catch (error) {\n    issues.push(\n      error instanceof Error ? error.message : \"Unsupported Node.js runtime.\"\n    );\n  }\n\n  if (!resolvedEnv.apiKey) {\n    issues.push(options.missingApiKeyIssue || genericMissingApiKeyIssue);\n  }\n\n  issues.push(...(options.getAdditionalIssues?.(resolvedEnv, env) ?? []));\n\n  return {\n    config:\n      options.buildConfig?.(resolvedEnv, env) ??\n      ({\n        baseURL: resolvedEnv.baseURL,\n        chatModel: resolvedEnv.chatModel,\n      } as TConfig),\n    isReady: issues.length === 0,\n    issues,\n    nodeVersion: process.version,\n  };\n}\n\nexport function createAiGatewayFromContract(\n  env: AiGatewayEnvRecord,\n  options: AiGatewayContractOptions\n): ReturnType<typeof createGateway> {\n  const { apiKey, baseURL } = readAiGatewayContractConfig(env, options);\n\n  return createGateway({\n    apiKey,\n    baseURL,\n  });\n}\n",
      "type": "registry:lib",
      "target": "@lib/ai-gateway/contract.ts"
    },
    {
      "path": "registry/generative-ui/lib/generative-ui/ui-contract.ts",
      "content": "import { z } from \"zod\";\n\nexport const comparisonRatingSchema = z.enum([\n  \"weak\",\n  \"mixed\",\n  \"strong\",\n  \"best\",\n]);\n\nexport const featureComparisonInputSchema = z.object({\n  criteria: z\n    .array(\n      z.object({\n        label: z.string().min(1).describe(\"Criterion name.\"),\n        scores: z\n          .array(\n            z.object({\n              option: z\n                .string()\n                .min(1)\n                .describe(\"The option name this score refers to.\"),\n              rating: comparisonRatingSchema.describe(\n                \"The relative score for this option on the criterion.\"\n              ),\n              summary: z\n                .string()\n                .min(1)\n                .describe(\"Short, user-facing reason for the rating.\"),\n            })\n          )\n          .min(1)\n          .describe(\"Scores for the compared options.\"),\n      })\n    )\n    .min(2)\n    .max(6)\n    .describe(\"Criteria used for the comparison matrix.\"),\n  options: z\n    .array(\n      z.object({\n        name: z.string().min(1).describe(\"Compared option name.\"),\n        summary: z.string().min(1).describe(\"Short description of the option.\"),\n      })\n    )\n    .min(2)\n    .max(5)\n    .describe(\"Options to compare.\"),\n  subject: z.string().min(1).describe(\"What is being compared.\"),\n  summary: z.string().min(1).describe(\"One-sentence comparison takeaway.\"),\n});\n\nexport const featureComparisonOutputSchema =\n  featureComparisonInputSchema.extend({\n    kind: z.literal(\"feature-comparison\"),\n  });\n\nexport const planRecommendationInputSchema = z.object({\n  alternatives: z\n    .array(\n      z.object({\n        name: z.string().min(1).describe(\"Alternative option name.\"),\n        tradeoff: z\n          .string()\n          .min(1)\n          .describe(\"Main tradeoff versus the recommendation.\"),\n      })\n    )\n    .min(1)\n    .max(5)\n    .describe(\"Credible alternatives considered.\"),\n  decision: z\n    .string()\n    .min(1)\n    .describe(\"The decision or choice the recommendation answers.\"),\n  nextSteps: z\n    .array(z.string().min(1))\n    .min(1)\n    .max(5)\n    .describe(\"Concrete next actions for the user.\"),\n  rationale: z\n    .array(\n      z.object({\n        detail: z.string().min(1).describe(\"Reasoning detail.\"),\n        label: z.string().min(1).describe(\"Reason label.\"),\n      })\n    )\n    .min(1)\n    .max(5)\n    .describe(\"Reasons supporting the recommended option.\"),\n  recommendedOption: z.object({\n    name: z.string().min(1).describe(\"Recommended option name.\"),\n    summary: z.string().min(1).describe(\"Why this option is recommended.\"),\n  }),\n  risks: z\n    .array(\n      z.object({\n        mitigation: z\n          .string()\n          .min(1)\n          .optional()\n          .describe(\"How to reduce this risk.\"),\n        risk: z.string().min(1).describe(\"Risk to watch.\"),\n      })\n    )\n    .max(5)\n    .describe(\"Risks or caveats for the recommendation.\"),\n});\n\nexport const planRecommendationOutputSchema =\n  planRecommendationInputSchema.extend({\n    kind: z.literal(\"plan-recommendation\"),\n  });\n\nexport type FeatureComparisonInput = z.infer<\n  typeof featureComparisonInputSchema\n>;\nexport type FeatureComparisonOutput = z.infer<\n  typeof featureComparisonOutputSchema\n>;\nexport type PlanRecommendationInput = z.infer<\n  typeof planRecommendationInputSchema\n>;\nexport type PlanRecommendationOutput = z.infer<\n  typeof planRecommendationOutputSchema\n>;\n\nexport function createFeatureComparison(\n  input: FeatureComparisonInput\n): FeatureComparisonOutput {\n  const parsedInput = featureComparisonInputSchema.parse(input);\n\n  return {\n    ...parsedInput,\n    kind: \"feature-comparison\",\n  };\n}\n\nexport function createPlanRecommendation(\n  input: PlanRecommendationInput\n): PlanRecommendationOutput {\n  const parsedInput = planRecommendationInputSchema.parse(input);\n\n  return {\n    ...parsedInput,\n    kind: \"plan-recommendation\",\n  };\n}\n",
      "type": "registry:lib",
      "target": "@lib/generative-ui/ui-contract.ts"
    },
    {
      "path": "registry/generative-ui/lib/generative-ui/env-source.ts",
      "content": "export function getGenerativeUiAppEnv() {\n  // biome-ignore lint/style/noProcessEnv: Registry source installs into consumer apps without this repo's env wrapper.\n  return process.env;\n}\n",
      "type": "registry:lib",
      "target": "@lib/generative-ui/env-source.ts"
    },
    {
      "path": "registry/generative-ui/lib/generative-ui/env.ts",
      "content": "import {\n  type AiGatewayContractConfig,\n  type AiGatewayContractSetupState,\n  type AiGatewayEnvRecord,\n  type AiGatewaySetupConfig,\n  buildAiGatewayContractSetupState,\n  createAiGatewayFromContract,\n  readAiGatewayContractConfig,\n} from \"@/lib/ai-gateway/contract\";\nimport { getGenerativeUiAppEnv } from \"./env-source\";\nimport {\n  DEFAULT_GENERATIVE_UI_CHAT_MODEL,\n  resolveGenerativeUiChatModel,\n} from \"./model\";\n\nexport const DEFAULT_GENERATIVE_UI_MODEL = DEFAULT_GENERATIVE_UI_CHAT_MODEL;\n\nexport type GenerativeUiEnv = AiGatewayEnvRecord;\n\nexport type GenerativeUiConfig = AiGatewayContractConfig;\n\nexport type GenerativeUiSetupState =\n  AiGatewayContractSetupState<AiGatewaySetupConfig>;\n\nexport type GenerativeUiGateway = ReturnType<\n  typeof createAiGatewayFromContract\n>;\n\nconst generativeUiContract = {\n  defaultChatModel: DEFAULT_GENERATIVE_UI_MODEL,\n  missingApiKeyError:\n    \"Missing AI_GATEWAY_API_KEY. Add it to .env.local before using generative UI.\",\n  missingApiKeyIssue:\n    \"AI_GATEWAY_API_KEY is missing. The demo can render, but chat requests will fail until it is configured.\",\n} as const;\n\nexport function getGenerativeUiEnv(): GenerativeUiEnv {\n  return getGenerativeUiAppEnv();\n}\n\nexport const readGenerativeUiEnv = getGenerativeUiEnv;\n\nexport function getGenerativeUiConfig(\n  env: GenerativeUiEnv = getGenerativeUiEnv()\n): GenerativeUiConfig {\n  return {\n    ...readAiGatewayContractConfig(env, generativeUiContract),\n    chatModel: resolveGenerativeUiChatModel(env),\n  };\n}\n\nexport function getGenerativeUiSetupState(\n  env: GenerativeUiEnv = getGenerativeUiEnv()\n): GenerativeUiSetupState {\n  return buildAiGatewayContractSetupState(env, {\n    ...generativeUiContract,\n    buildConfig: (resolvedEnv) => ({\n      baseURL: resolvedEnv.baseURL,\n      chatModel: resolveGenerativeUiChatModel(env),\n    }),\n  });\n}\n\nexport function createGenerativeUiGateway(\n  env: GenerativeUiEnv = getGenerativeUiEnv()\n): GenerativeUiGateway {\n  return createAiGatewayFromContract(env, generativeUiContract);\n}\n",
      "type": "registry:lib",
      "target": "@lib/generative-ui/env.ts"
    },
    {
      "path": "registry/generative-ui/lib/generative-ui/model.ts",
      "content": "export const DEFAULT_GENERATIVE_UI_CHAT_MODEL = \"openai/gpt-5-mini\";\nexport const GENERATIVE_UI_CHAT_MODEL_ENV_KEY = \"GENERATIVE_UI_CHAT_MODEL\";\n\nexport const GENERATIVE_UI_SEARCH_TOOL_NAME = \"web_search\";\n\nexport const GENERATIVE_UI_PROVIDER_OPTIONS = {\n  openai: {\n    forceReasoning: true,\n    reasoningSummary: \"detailed\",\n    textVerbosity: \"low\",\n  },\n} as const;\n\ntype GenerativeUiModelEnv = Record<string, string | undefined>;\n\nexport function resolveGenerativeUiChatModel(\n  env: GenerativeUiModelEnv = {}\n): string {\n  return (\n    env[GENERATIVE_UI_CHAT_MODEL_ENV_KEY] || DEFAULT_GENERATIVE_UI_CHAT_MODEL\n  );\n}\n",
      "type": "registry:lib",
      "target": "@lib/generative-ui/model.ts"
    },
    {
      "path": "registry/generative-ui/lib/generative-ui/chat.ts",
      "content": "import { createOpenAI } from \"@ai-sdk/openai\";\nimport { convertToModelMessages, streamText, tool, type UIMessage } from \"ai\";\n\nimport {\n  createFeatureComparison,\n  createPlanRecommendation,\n  featureComparisonInputSchema,\n  planRecommendationInputSchema,\n} from \"@/lib/generative-ui/ui-contract\";\nimport {\n  type GenerativeUiEnv,\n  getGenerativeUiConfig,\n  getGenerativeUiEnv,\n} from \"./env\";\nimport {\n  GENERATIVE_UI_PROVIDER_OPTIONS,\n  GENERATIVE_UI_SEARCH_TOOL_NAME,\n} from \"./model\";\n\nconst systemPrompt = [\n  \"You are the Generative UI Agent Demo.\",\n  \"Answer broad user questions directly and choose visual tool output when a comparison or recommendation would make the answer easier to evaluate.\",\n  \"Use web_search when current public information would materially improve the answer, including market, pricing, release, competitor, or time-sensitive questions.\",\n  \"Skip web_search for stable technical, architectural, or domain-knowledge questions where durable reasoning is enough.\",\n  \"Use showFeatureComparison for comparisons across options, criteria, capabilities, or tradeoffs.\",\n  \"Use showPlanRecommendation when the user asks what to pick, what to do next, or which option fits best.\",\n  \"When web_search informs the answer, rely on the message-level source channel for citations and keep source URLs out of the selected UI tool input.\",\n  \"Keep surrounding prose concise; let the selected UI tool carry the main answer.\",\n].join(\" \");\n\nconst trailingSlashPattern = /\\/$/;\nconst v3AiSuffixPattern = /\\/v3\\/ai$/;\n\nfunction resolveOpenAICompatibleBaseURL(baseURL: string) {\n  const normalizedBaseURL = baseURL.replace(trailingSlashPattern, \"\");\n\n  if (normalizedBaseURL.endsWith(\"/v1\")) {\n    return normalizedBaseURL;\n  }\n\n  if (normalizedBaseURL.endsWith(\"/v3/ai\")) {\n    return normalizedBaseURL.replace(v3AiSuffixPattern, \"/v1\");\n  }\n\n  throw new Error(\n    `Generative UI expects AI_GATEWAY_BASE_URL to end with /v3/ai or /v1. Received: ${baseURL}`\n  );\n}\n\nfunction summarizeFeatureComparisonOutput(output: unknown) {\n  if (!output || typeof output !== \"object\") {\n    return;\n  }\n\n  const comparison = output as {\n    kind?: unknown;\n    subject?: unknown;\n    summary?: unknown;\n  };\n\n  if (\n    comparison.kind !== \"feature-comparison\" ||\n    typeof comparison.subject !== \"string\" ||\n    typeof comparison.summary !== \"string\"\n  ) {\n    return;\n  }\n\n  return `Displayed comparison: ${comparison.subject}. ${comparison.summary}`;\n}\n\nfunction summarizePlanRecommendationOutput(output: unknown) {\n  if (!output || typeof output !== \"object\") {\n    return;\n  }\n\n  const recommendation = output as {\n    decision?: unknown;\n    kind?: unknown;\n    recommendedOption?: {\n      name?: unknown;\n      summary?: unknown;\n    };\n  };\n\n  if (\n    recommendation.kind !== \"plan-recommendation\" ||\n    typeof recommendation.decision !== \"string\" ||\n    typeof recommendation.recommendedOption?.name !== \"string\"\n  ) {\n    return;\n  }\n\n  const summary =\n    typeof recommendation.recommendedOption.summary === \"string\"\n      ? ` ${recommendation.recommendedOption.summary}`\n      : \"\";\n\n  return `Displayed recommendation for ${recommendation.decision}: ${recommendation.recommendedOption.name}.${summary}`;\n}\n\nfunction summarizeGenerativeUiToolOutput(\n  part: UIMessage[\"parts\"][number]\n): string | undefined {\n  if (!(\"output\" in part) || part.state !== \"output-available\") {\n    return;\n  }\n\n  if (part.type === \"tool-showFeatureComparison\") {\n    return summarizeFeatureComparisonOutput(part.output);\n  }\n\n  if (part.type === \"tool-showPlanRecommendation\") {\n    return summarizePlanRecommendationOutput(part.output);\n  }\n\n  return;\n}\n\nfunction isNonEmptyTextPart(\n  part: UIMessage[\"parts\"][number]\n): part is UIMessage[\"parts\"][number] & { text: string; type: \"text\" } {\n  return (\n    part.type === \"text\" &&\n    \"text\" in part &&\n    typeof part.text === \"string\" &&\n    part.text.trim().length > 0\n  );\n}\n\nfunction getModelHistoryTextParts(message: UIMessage) {\n  const textParts = message.parts.filter(isNonEmptyTextPart).map((part) => ({\n    text: part.text,\n    type: \"text\" as const,\n  }));\n\n  const toolSummaries = message.parts\n    .map(summarizeGenerativeUiToolOutput)\n    .filter((text): text is string => Boolean(text))\n    .map((text) => ({\n      text,\n      type: \"text\" as const,\n    }));\n\n  return [...textParts, ...toolSummaries];\n}\n\nexport function prepareGenerativeUiModelMessages(messages: UIMessage[]) {\n  return messages\n    .map((message) => ({\n      ...message,\n      parts: getModelHistoryTextParts(message),\n    }))\n    .filter(\n      (message) => message.role === \"user\" || message.parts.length > 0\n    ) satisfies UIMessage[];\n}\n\nexport async function streamGenerativeUiChat(\n  messages: UIMessage[],\n  env: GenerativeUiEnv = getGenerativeUiEnv()\n) {\n  const { apiKey, baseURL, chatModel } = getGenerativeUiConfig(env);\n  const openai = createOpenAI({\n    apiKey,\n    baseURL: resolveOpenAICompatibleBaseURL(baseURL),\n    name: \"gateway-openai\",\n  });\n  const runId = crypto.randomUUID();\n  const startedAt = Date.now();\n  const messageMetadataBase = {\n    model: chatModel,\n    runId,\n    searchTool: GENERATIVE_UI_SEARCH_TOOL_NAME,\n    startedAt,\n  };\n\n  const result = streamText({\n    experimental_telemetry: {\n      functionId: \"generative-ui.run\",\n      isEnabled: true,\n      metadata: {\n        demo: \"generative-ui\",\n        runId,\n        searchTool: GENERATIVE_UI_SEARCH_TOOL_NAME,\n      },\n      recordInputs: true,\n      recordOutputs: true,\n    },\n    messages: await convertToModelMessages(\n      prepareGenerativeUiModelMessages(messages)\n    ),\n    model: openai(chatModel),\n    providerOptions: GENERATIVE_UI_PROVIDER_OPTIONS,\n    system: systemPrompt,\n    tools: {\n      [GENERATIVE_UI_SEARCH_TOOL_NAME]: openai.tools.webSearch({\n        searchContextSize: \"medium\",\n      }),\n      showFeatureComparison: tool({\n        description:\n          \"Render a structured comparison matrix for options, criteria, and tradeoffs.\",\n        inputSchema: featureComparisonInputSchema,\n        execute: createFeatureComparison,\n      }),\n      showPlanRecommendation: tool({\n        description:\n          \"Render a structured recommendation card for a decision, rationale, risks, and next steps.\",\n        inputSchema: planRecommendationInputSchema,\n        execute: createPlanRecommendation,\n      }),\n    },\n  });\n\n  return result.toUIMessageStreamResponse({\n    messageMetadata: ({ part }) => {\n      if (part.type === \"start\") {\n        return messageMetadataBase;\n      }\n\n      if (part.type === \"finish\") {\n        return {\n          ...messageMetadataBase,\n          finishReason: part.finishReason,\n          finishedAt: Date.now(),\n          totalUsage: part.totalUsage,\n        };\n      }\n\n      return;\n    },\n    originalMessages: messages,\n    sendReasoning: true,\n    sendSources: true,\n  });\n}\n",
      "type": "registry:lib",
      "target": "@lib/generative-ui/chat.ts"
    },
    {
      "path": "registry/generative-ui/lib/generative-ui/runtime.ts",
      "content": "import { type UIMessage, validateUIMessages } from \"ai\";\n\nimport {\n  type GenerativeUiEnv,\n  getGenerativeUiEnv,\n  getGenerativeUiSetupState,\n} from \"./env\";\n\ninterface GenerativeUiRequestBody {\n  messages?: UIMessage[];\n}\n\nexport interface GenerativeUiRuntimeState {\n  chatModel: string;\n  isChatAvailable: boolean;\n  nodeVersion: string;\n  setupMessage: string | null;\n  statusLabel: \"Ready\" | \"Setup required\";\n}\n\ninterface GenerativeUiRequestDependencies {\n  streamGenerativeUiChat: (\n    messages: UIMessage[],\n    env: GenerativeUiEnv\n  ) => Promise<Response>;\n}\n\nconst invalidMessagesError = 'Expected a JSON body with a \"messages\" array.';\nconst invalidUiMessagesError =\n  'Expected each \"messages\" entry to match the UIMessage format.';\nconst malformedJsonError = \"Expected a valid JSON request body.\";\n\nexport function getGenerativeUiRuntimeState(\n  env: GenerativeUiEnv = getGenerativeUiEnv()\n): GenerativeUiRuntimeState {\n  const setup = getGenerativeUiSetupState(env);\n\n  return {\n    chatModel: setup.config.chatModel,\n    isChatAvailable: setup.isReady,\n    nodeVersion: setup.nodeVersion,\n    setupMessage: setup.issues.length > 0 ? setup.issues.join(\" \") : null,\n    statusLabel: setup.isReady ? \"Ready\" : \"Setup required\",\n  };\n}\n\nasync function readGenerativeUiMessages(body: unknown): Promise<UIMessage[]> {\n  const { messages } = (body ?? {}) as GenerativeUiRequestBody;\n\n  if (!Array.isArray(messages)) {\n    throw new Error(invalidMessagesError);\n  }\n\n  try {\n    return await validateUIMessages({ messages });\n  } catch {\n    throw new Error(invalidUiMessagesError);\n  }\n}\n\nasync function streamGenerativeUiChatFromRuntime(\n  messages: UIMessage[],\n  env: GenerativeUiEnv\n) {\n  const { streamGenerativeUiChat } = await import(\"./chat\");\n\n  return streamGenerativeUiChat(messages, env);\n}\n\nexport async function handleGenerativeUiRequest(\n  request: Request,\n  env: GenerativeUiEnv = getGenerativeUiEnv(),\n  dependencies: GenerativeUiRequestDependencies = {\n    streamGenerativeUiChat: streamGenerativeUiChatFromRuntime,\n  }\n) {\n  const runtimeState = getGenerativeUiRuntimeState(env);\n\n  if (!runtimeState.isChatAvailable) {\n    return Response.json(\n      {\n        error: runtimeState.setupMessage,\n      },\n      { status: 500 }\n    );\n  }\n\n  let messages: UIMessage[];\n\n  try {\n    messages = await readGenerativeUiMessages(await request.json());\n  } catch (error) {\n    if (error instanceof SyntaxError) {\n      return Response.json(\n        {\n          error: malformedJsonError,\n        },\n        { status: 400 }\n      );\n    }\n\n    if (\n      error instanceof Error &&\n      [invalidMessagesError, invalidUiMessagesError].includes(error.message)\n    ) {\n      return Response.json(\n        {\n          error: error.message,\n        },\n        { status: 400 }\n      );\n    }\n\n    throw error;\n  }\n\n  return dependencies.streamGenerativeUiChat(messages, env);\n}\n",
      "type": "registry:lib",
      "target": "@lib/generative-ui/runtime.ts"
    }
  ],
  "envVars": {
    "AI_GATEWAY_API_KEY": "",
    "AI_GATEWAY_BASE_URL": "https://ai-gateway.vercel.sh/v3/ai",
    "GENERATIVE_UI_CHAT_MODEL": "openai/gpt-5-mini"
  },
  "docs": "Install into a Next.js App Router project initialized with shadcn/ui. This item installs a chat route, AI SDK UI message rendering, OpenAI hosted web search through AI Gateway, and two custom tool-output components for comparison and recommendation outputs.",
  "type": "registry:block"
}
