{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "sandbox-agent",
  "title": "Sandbox Agent",
  "description": "A sandbox-backed prototype builder that gives each chat an isolated workspace and a live preview tab.",
  "dependencies": [
    "@ai-sdk/react",
    "@base-ui/react",
    "@phosphor-icons/react",
    "@vercel/sandbox",
    "ai",
    "class-variance-authority",
    "lucide-react",
    "zod"
  ],
  "registryDependencies": [
    "utils",
    "badge",
    "breadcrumb",
    "button",
    "tabs",
    "textarea",
    "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/shimmer.json",
    "https://elements.ai-sdk.dev/api/registry/tool.json",
    "https://elements.ai-sdk.dev/api/registry/web-preview.json"
  ],
  "files": [
    {
      "path": "registry/sandbox-agent/app/demos/sandbox-agent/page.tsx",
      "content": "import { SandboxAgentScreen } from \"@/components/sandbox-agent/sandbox-agent-screen\";\n\nexport default function SandboxAgentPage() {\n  return <SandboxAgentScreen />;\n}\n",
      "type": "registry:page",
      "target": "app/demos/sandbox-agent/page.tsx"
    },
    {
      "path": "registry/sandbox-agent/app/api/demos/sandbox-agent/route.ts",
      "content": "import { handleSandboxAgentRequest } from \"@/lib/sandbox-agent/server/request\";\n\nexport const runtime = \"nodejs\";\n\nexport function POST(request: Request) {\n  return handleSandboxAgentRequest(request);\n}\n",
      "type": "registry:page",
      "target": "app/api/demos/sandbox-agent/route.ts"
    },
    {
      "path": "registry/sandbox-agent/app/api/demos/sandbox-agent/preview-status/route.ts",
      "content": "const PREVIEW_ERROR_CODE_RE = /^[A-Z0-9_]+$/;\n\nfunction readPreviewErrorCode(response: Response, body: string) {\n  const headerCode = response.headers.get(\"x-vercel-error\");\n\n  if (headerCode) {\n    return headerCode;\n  }\n\n  const bodyCode = body\n    .split(\"\\n\")\n    .map((line) => line.trim())\n    .find((line) => PREVIEW_ERROR_CODE_RE.test(line));\n\n  return bodyCode ?? null;\n}\n\nfunction trimBody(body: string) {\n  const trimmed = body.trim();\n\n  if (!trimmed) {\n    return null;\n  }\n\n  return trimmed.slice(0, 280);\n}\n\nexport async function GET(request: Request) {\n  const { searchParams } = new URL(request.url);\n  const url = searchParams.get(\"url\");\n\n  if (!url) {\n    return Response.json(\n      {\n        errorCode: \"MISSING_URL\",\n        message: \"A preview URL is required.\",\n        ok: false,\n        status: null,\n        statusText: null,\n      },\n      { status: 400 }\n    );\n  }\n\n  let parsedUrl: URL;\n\n  try {\n    parsedUrl = new URL(url);\n  } catch {\n    return Response.json(\n      {\n        errorCode: \"INVALID_URL\",\n        message: \"The preview URL is not a valid absolute URL.\",\n        ok: false,\n        status: null,\n        statusText: null,\n      },\n      { status: 400 }\n    );\n  }\n\n  if (![\"http:\", \"https:\"].includes(parsedUrl.protocol)) {\n    return Response.json(\n      {\n        errorCode: \"INVALID_PROTOCOL\",\n        message: \"The preview URL must use http or https.\",\n        ok: false,\n        status: null,\n        statusText: null,\n      },\n      { status: 400 }\n    );\n  }\n\n  try {\n    const response = await fetch(parsedUrl, {\n      cache: \"no-store\",\n      redirect: \"follow\",\n    });\n    const body = trimBody(await response.text());\n\n    return Response.json({\n      errorCode: response.ok\n        ? null\n        : readPreviewErrorCode(response, body ?? \"\"),\n      message: response.ok ? null : body,\n      ok: response.ok,\n      status: response.status,\n      statusText: response.statusText || null,\n    });\n  } catch (error) {\n    return Response.json({\n      errorCode: \"FETCH_FAILED\",\n      message:\n        error instanceof Error ? error.message : \"Failed to check preview URL.\",\n      ok: false,\n      status: null,\n      statusText: null,\n    });\n  }\n}\n",
      "type": "registry:page",
      "target": "app/api/demos/sandbox-agent/preview-status/route.ts"
    },
    {
      "path": "registry/sandbox-agent/app/api/demos/sandbox-agent/local-preview/route.ts",
      "content": "import { posix as posixPath } from \"node:path\";\n\nfunction normalizeEntryPath(entryPath: string) {\n  const normalizedPath = posixPath\n    .normalize(entryPath.startsWith(\"/\") ? entryPath : `/${entryPath}`)\n    .replace(/^(\\.\\.(\\/|\\\\|$))+/, \"\");\n\n  return normalizedPath === \"/\" ? \"/index.html\" : normalizedPath;\n}\n\nfunction buildPathPreviewUrl({\n  directory,\n  entryPath,\n  request,\n  sessionId,\n}: {\n  directory: string;\n  entryPath: string;\n  request: Request;\n  sessionId: string;\n}) {\n  const normalizedDirectory =\n    directory === \".\" ? \"\" : directory.replace(/^\\/+/, \"\");\n  const previewPath = posixPath.join(\n    normalizedDirectory,\n    normalizeEntryPath(entryPath).slice(1)\n  );\n  const url = new URL(request.url);\n  url.pathname = [\n    \"/api/demos/sandbox-agent/local-preview\",\n    encodeURIComponent(sessionId),\n    ...previewPath.split(\"/\").filter(Boolean).map(encodeURIComponent),\n  ].join(\"/\");\n  url.search = \"\";\n\n  return url;\n}\n\nexport async function GET(request: Request) {\n  const { searchParams } = new URL(request.url);\n  const sessionId = searchParams.get(\"sessionId\");\n  const directory = searchParams.get(\"directory\") ?? \".\";\n  const entryPath = searchParams.get(\"path\") ?? \"/index.html\";\n\n  if (!sessionId) {\n    return new Response(\"Missing sessionId.\", { status: 400 });\n  }\n\n  return Response.redirect(\n    buildPathPreviewUrl({\n      directory,\n      entryPath,\n      request,\n      sessionId,\n    }),\n    307\n  );\n}\n",
      "type": "registry:page",
      "target": "app/api/demos/sandbox-agent/local-preview/route.ts"
    },
    {
      "path": "registry/sandbox-agent/app/api/demos/sandbox-agent/local-preview/[sessionId]/[...path]/route.ts",
      "content": "import { posix as posixPath } from \"node:path\";\n\nimport { getSandboxAgentEnv } from \"@/lib/sandbox-agent/server/env\";\nimport { getSharedSandboxAgentSessionRegistry } from \"@/lib/sandbox-agent/server/session\";\n\nconst contentTypes = new Map([\n  [\".css\", \"text/css; charset=utf-8\"],\n  [\".html\", \"text/html; charset=utf-8\"],\n  [\".js\", \"text/javascript; charset=utf-8\"],\n  [\".json\", \"application/json; charset=utf-8\"],\n  [\".mjs\", \"text/javascript; charset=utf-8\"],\n  [\".svg\", \"image/svg+xml; charset=utf-8\"],\n  [\".txt\", \"text/plain; charset=utf-8\"],\n]);\n\nfunction normalizePreviewPath(pathSegments: string[]) {\n  const normalizedPath = posixPath\n    .normalize(`/${pathSegments.join(\"/\")}`)\n    .replace(/^(\\.\\.(\\/|\\\\|$))+/, \"\");\n\n  return normalizedPath === \"/\" ? \"/index.html\" : normalizedPath;\n}\n\nfunction getContentType(targetPath: string) {\n  return (\n    contentTypes.get(posixPath.extname(targetPath)) ??\n    \"text/plain; charset=utf-8\"\n  );\n}\n\nexport async function GET(\n  request: Request,\n  {\n    params,\n  }: {\n    params: Promise<{\n      path?: string[];\n      sessionId: string;\n    }>;\n  }\n) {\n  const { path = [], sessionId } = await params;\n  const registry = await getSharedSandboxAgentSessionRegistry(\n    getSandboxAgentEnv(),\n    {\n      localPreviewBaseUrl: new URL(request.url).origin,\n    }\n  );\n  const session = registry.getSession(sessionId);\n  const targetPath = normalizePreviewPath(path).slice(1);\n\n  try {\n    return new Response(await session.readFile(targetPath), {\n      headers: {\n        \"content-type\": getContentType(targetPath),\n      },\n    });\n  } catch (error) {\n    return new Response(\n      error instanceof Error ? error.message : \"Preview file not found.\",\n      {\n        headers: {\n          \"content-type\": \"text/plain; charset=utf-8\",\n        },\n        status: 404,\n      }\n    );\n  }\n}\n",
      "type": "registry:page",
      "target": "app/api/demos/sandbox-agent/local-preview/[sessionId]/[...path]/route.ts"
    },
    {
      "path": "registry/sandbox-agent/components/sandbox-agent/sandbox-agent-screen.tsx",
      "content": "import { Badge } from \"@/components/ui/badge\";\nimport {\n  Breadcrumb,\n  BreadcrumbItem,\n  BreadcrumbLink,\n  BreadcrumbList,\n  BreadcrumbPage,\n  BreadcrumbSeparator,\n} from \"@/components/ui/breadcrumb\";\nimport { ArrowLeft } from \"lucide-react\";\n\nimport { getSandboxAgentRuntimeState } from \"@/lib/sandbox-agent/server/runtime\";\nimport { SandboxAgentWorkspace } from \"./sandbox-agent-workspace\";\n\nexport async function SandboxAgentScreen() {\n  const runtimeState = await getSandboxAgentRuntimeState();\n\n  return (\n    <main className=\"min-h-svh bg-background text-foreground\">\n      <div className=\"mx-auto flex w-full max-w-7xl flex-col gap-6 px-4 py-6 md:px-6\">\n        <header className=\"grid gap-4 border border-foreground/10 bg-background px-4 py-5 md:grid-cols-[minmax(0,1fr)_auto] md:items-end\">\n          <div className=\"space-y-2\">\n            <Breadcrumb>\n              <BreadcrumbList className=\"text-[11px] text-muted-foreground uppercase tracking-[0.2em]\">\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                    Sandbox Workspace Agent\n                  </BreadcrumbPage>\n                </BreadcrumbItem>\n              </BreadcrumbList>\n            </Breadcrumb>\n            <h1 className=\"max-w-3xl font-medium text-2xl tracking-tight\">\n              Build a frontend prototype in an isolated workspace and expose the\n              live result\n            </h1>\n            <p className=\"max-w-3xl text-muted-foreground text-sm/relaxed\">\n              This workspace gives each chat an isolated project directory, lets\n              the model write HTML, CSS, and JavaScript files, and renders the\n              generated app through AI Elements WebPreview.\n            </p>\n          </div>\n\n          <div className=\"flex flex-wrap items-center gap-2\">\n            <Badge variant=\"outline\">{runtimeState.statusLabel}</Badge>\n            <Badge variant=\"outline\">{runtimeState.sandboxProvider}</Badge>\n            <Badge variant=\"outline\">{runtimeState.chatModel}</Badge>\n          </div>\n        </header>\n\n        <div className=\"lg:h-svh\">\n          <SandboxAgentWorkspace\n            chatModel={runtimeState.chatModel}\n            isChatAvailable={runtimeState.isChatAvailable}\n            nodeVersion={runtimeState.nodeVersion}\n            previewPort={runtimeState.previewPort}\n            sandboxProvider={runtimeState.sandboxProvider}\n            setupMessage={runtimeState.setupMessage}\n          />\n        </div>\n      </div>\n    </main>\n  );\n}\n",
      "type": "registry:component",
      "target": "@components/sandbox-agent/sandbox-agent-screen.tsx"
    },
    {
      "path": "registry/sandbox-agent/components/sandbox-agent/sandbox-agent-workspace.tsx",
      "content": "\"use client\";\n\nimport { Tabs, TabsList, TabsTrigger } from \"@/components/ui/tabs\";\n\nimport { SandboxConversationPane } from \"./sandbox-conversation-pane\";\nimport { SandboxPreviewPane } from \"./sandbox-preview-pane\";\nimport { SandboxRuntimeSidebar } from \"./sandbox-runtime-sidebar\";\nimport { useSandboxAgentWorkspaceViewModel } from \"./use-sandbox-agent-workspace-view-model\";\n\nexport interface SandboxAgentWorkspaceProps {\n  chatModel: string;\n  isChatAvailable: boolean;\n  nodeVersion: string;\n  previewPort: number;\n  sandboxProvider: string;\n  setupMessage: string | null;\n}\n\nexport function SandboxAgentWorkspace({\n  chatModel,\n  isChatAvailable,\n  nodeVersion,\n  previewPort,\n  sandboxProvider,\n  setupMessage,\n}: SandboxAgentWorkspaceProps) {\n  const workspace = useSandboxAgentWorkspaceViewModel();\n\n  return (\n    <div className=\"grid min-h-[70svh] gap-4 lg:h-full lg:min-h-0 lg:grid-cols-[minmax(0,1fr)_22rem]\">\n      <section className=\"flex min-h-[70svh] 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        {workspace.error ? (\n          <div className=\"border-foreground/10 border-b px-4 py-3 text-destructive text-xs/relaxed\">\n            {workspace.error.message}\n          </div>\n        ) : null}\n\n        {workspace.isTabsMounted ? (\n          <Tabs\n            className=\"flex min-h-0 flex-1 flex-col gap-0\"\n            onValueChange={(value) =>\n              workspace.actions.onTabChange(value as \"conversation\" | \"preview\")\n            }\n            value={workspace.activeTab}\n          >\n            <div className=\"border-foreground/10 border-b px-4 py-2\">\n              <TabsList className=\"grid w-fit grid-cols-2\">\n                <TabsTrigger value=\"conversation\">Conversation</TabsTrigger>\n                <TabsTrigger value=\"preview\">Preview</TabsTrigger>\n              </TabsList>\n            </div>\n\n            <SandboxConversationPane\n              chatModel={chatModel}\n              hasMessages={workspace.hasMessages}\n              isBusy={workspace.isBusy}\n              isChatAvailable={isChatAvailable}\n              messages={workspace.messages}\n              onOpenPreview={workspace.actions.onOpenPreview}\n              onRegenerate={workspace.actions.onRegenerate}\n              onSendMessage={workspace.actions.onSendMessage}\n              onStop={workspace.actions.onStop}\n              samplePrompts={workspace.samplePrompts}\n              sandboxProvider={sandboxProvider}\n              status={workspace.status}\n            />\n\n            <SandboxPreviewPane\n              latestPreview={workspace.latestPreview}\n              previewState={workspace.previewState}\n            />\n          </Tabs>\n        ) : (\n          <div className=\"flex min-h-0 flex-1 flex-col\">\n            <div className=\"border-foreground/10 border-b px-4 py-2\">\n              <div className=\"h-8 w-48 bg-muted\" />\n            </div>\n            <div className=\"min-h-0 flex-1\" />\n          </div>\n        )}\n      </section>\n\n      <SandboxRuntimeSidebar\n        nodeVersion={nodeVersion}\n        previewPort={previewPort}\n        sandboxProvider={sandboxProvider}\n      />\n    </div>\n  );\n}\n",
      "type": "registry:component",
      "target": "@components/sandbox-agent/sandbox-agent-workspace.tsx"
    },
    {
      "path": "registry/sandbox-agent/components/sandbox-agent/sandbox-conversation-pane.tsx",
      "content": "\"use client\";\n\nimport type { useChat } from \"@ai-sdk/react\";\nimport { StopIcon, WrenchIcon } from \"@phosphor-icons/react\";\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 { Badge } from \"@/components/ui/badge\";\nimport { Button } from \"@/components/ui/button\";\nimport { TabsContent } from \"@/components/ui/tabs\";\nimport { cn } from \"@/lib/utils\";\nimport type { UIMessage } from \"ai\";\n\nimport { SandboxAgentAssistantTrace } from \"./sandbox-agent-assistant-trace\";\nimport { getTextContent } from \"./sandbox-agent-model\";\n\ninterface SandboxConversationPaneProps {\n  chatModel: string;\n  hasMessages: boolean;\n  isBusy: boolean;\n  isChatAvailable: boolean;\n  messages: UIMessage[];\n  onOpenPreview: () => void;\n  onRegenerate: () => void;\n  onSendMessage: (text: string) => void;\n  onStop: () => void;\n  samplePrompts: readonly string[];\n  sandboxProvider: string;\n  status: ReturnType<typeof useChat>[\"status\"];\n}\n\nexport function SandboxConversationPane({\n  chatModel,\n  hasMessages,\n  isBusy,\n  isChatAvailable,\n  messages,\n  onOpenPreview,\n  onRegenerate,\n  onSendMessage,\n  onStop,\n  samplePrompts,\n  sandboxProvider,\n  status,\n}: SandboxConversationPaneProps) {\n  return (\n    <TabsContent className=\"mt-0 min-h-0 flex-1\" value=\"conversation\">\n      <div className=\"flex h-full min-h-0 flex-col\">\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 ? (\n              messages.map((message, index) => (\n                <Message from={message.role} key={message.id}>\n                  <MessageContent\n                    className={cn(\n                      \"space-y-4\",\n                      message.role === \"assistant\" ? \"max-w-4xl\" : \"max-w-2xl\"\n                    )}\n                  >\n                    {message.role === \"assistant\" ? (\n                      <SandboxAgentAssistantTrace\n                        isLastMessage={index === messages.length - 1}\n                        isStreaming={isBusy}\n                        message={message}\n                        onOpenPreview={onOpenPreview}\n                      />\n                    ) : (\n                      <MessageResponse>\n                        {getTextContent(message)}\n                      </MessageResponse>\n                    )}\n                  </MessageContent>\n                </Message>\n              ))\n            ) : (\n              <ConversationEmptyState\n                description=\"Ask the agent to scaffold a static frontend prototype, edit files in the sandbox workspace, and expose a live preview.\"\n                icon={<WrenchIcon className=\"size-5\" />}\n                title=\"Prototype 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 }) => onSendMessage(text)}>\n              <PromptInputBody>\n                <PromptInputTextarea\n                  disabled={!isChatAvailable || isBusy}\n                  placeholder=\"Describe a frontend prototype you want to build in the sandbox.\"\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\">ToolLoopAgent</Badge>\n                  <Badge variant=\"outline\">{sandboxProvider}</Badge>\n                  <Badge variant=\"outline\">{chatModel}</Badge>\n                </div>\n                <div className=\"flex items-center gap-2\">\n                  {isBusy ? (\n                    <Button\n                      onClick={onStop}\n                      size=\"sm\"\n                      type=\"button\"\n                      variant=\"outline\"\n                    >\n                      <StopIcon className=\"size-3.5\" />\n                      Stop\n                    </Button>\n                  ) : null}\n                  {hasMessages ? (\n                    <Button\n                      onClick={onRegenerate}\n                      size=\"sm\"\n                      type=\"button\"\n                      variant=\"outline\"\n                    >\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 flex flex-wrap gap-2\">\n                {samplePrompts.map((prompt) => (\n                  <Button\n                    key={prompt}\n                    onClick={() => onSendMessage(prompt)}\n                    size=\"sm\"\n                    type=\"button\"\n                    variant=\"outline\"\n                  >\n                    <WrenchIcon className=\"size-3.5\" />\n                    {prompt}\n                  </Button>\n                ))}\n              </div>\n            )}\n          </div>\n        </div>\n      </div>\n    </TabsContent>\n  );\n}\n",
      "type": "registry:component",
      "target": "@components/sandbox-agent/sandbox-conversation-pane.tsx"
    },
    {
      "path": "registry/sandbox-agent/components/sandbox-agent/sandbox-preview-pane.tsx",
      "content": "\"use client\";\n\nimport {\n  ArrowClockwiseIcon,\n  ArrowLeftIcon,\n  ArrowRightIcon,\n  ArrowSquareOutIcon,\n  ArrowsInIcon,\n  ArrowsOutIcon,\n  CopySimpleIcon,\n} from \"@phosphor-icons/react\";\nimport {\n  WebPreview,\n  WebPreviewBody,\n  WebPreviewConsole,\n  WebPreviewNavigation,\n  WebPreviewNavigationButton,\n  WebPreviewUrl,\n} from \"@/components/ai-elements/web-preview\";\nimport { TabsContent } from \"@/components/ui/tabs\";\n\nimport {\n  formatPreviewStatusSummary,\n  parsePreviewStatus,\n} from \"./preview-state\";\nimport {\n  type SandboxPreviewState,\n  usePreviewHealth,\n} from \"./use-sandbox-preview-state\";\n\ninterface LatestPreview {\n  directory: string | null;\n  entryPath: string | null;\n  port: number | null;\n  url: string;\n}\n\ninterface SandboxPreviewPaneProps {\n  latestPreview: LatestPreview | null;\n  previewState: SandboxPreviewState;\n}\n\nexport function SandboxPreviewPane({\n  latestPreview,\n  previewState,\n}: SandboxPreviewPaneProps) {\n  const previewHealth = usePreviewHealth(\n    previewState.activePreviewUrl,\n    previewState.previewReloadKey,\n    previewState.appendPreviewLog\n  );\n\n  if (!latestPreview?.url) {\n    return (\n      <TabsContent className=\"mt-0 min-h-0 flex-1\" value=\"preview\">\n        <div className=\"flex size-full items-center justify-center p-6 text-center\">\n          <div className=\"max-w-md space-y-2\">\n            <p className=\"font-medium text-foreground text-sm\">\n              Preview is not running yet\n            </p>\n            <p className=\"text-muted-foreground text-sm\">\n              A live preview will appear here after the agent starts a sandbox\n              preview for the generated prototype.\n            </p>\n          </div>\n        </div>\n      </TabsContent>\n    );\n  }\n\n  return (\n    <TabsContent className=\"mt-0 min-h-0 flex-1\" value=\"preview\">\n      <div className=\"size-full\" ref={previewState.previewContainerRef}>\n        <WebPreview\n          className=\"size-full rounded-none border-0 bg-transparent\"\n          defaultUrl=\"/\"\n          key={`${previewState.activePreviewUrl}:${previewState.previewReloadKey}`}\n          onUrlChange={previewState.handlePreviewUrlChange}\n          style={{ height: \"100%\" }}\n        >\n          <WebPreviewNavigation>\n            <WebPreviewNavigationButton\n              disabled={!previewState.canGoBack}\n              onClick={previewState.handleGoBack}\n              tooltip=\"Go back\"\n            >\n              <ArrowLeftIcon className=\"size-4\" />\n            </WebPreviewNavigationButton>\n            <WebPreviewNavigationButton\n              disabled={!previewState.canGoForward}\n              onClick={previewState.handleGoForward}\n              tooltip=\"Go forward\"\n            >\n              <ArrowRightIcon className=\"size-4\" />\n            </WebPreviewNavigationButton>\n            <WebPreviewNavigationButton\n              onClick={previewState.handleReload}\n              tooltip=\"Reload\"\n            >\n              <ArrowClockwiseIcon className=\"size-4\" />\n            </WebPreviewNavigationButton>\n            <WebPreviewUrl\n              data-preview-url-input=\"true\"\n              value={previewState.activePreviewUrl}\n            />\n            <WebPreviewNavigationButton\n              onClick={previewState.handleCopy}\n              tooltip=\"Copy preview URL\"\n            >\n              <CopySimpleIcon className=\"size-4\" />\n            </WebPreviewNavigationButton>\n            <WebPreviewNavigationButton\n              onClick={previewState.handleOpenInNewTab}\n              tooltip=\"Open in new tab\"\n            >\n              <ArrowSquareOutIcon className=\"size-4\" />\n            </WebPreviewNavigationButton>\n            <WebPreviewNavigationButton\n              onClick={previewState.handleToggleFullscreen}\n              tooltip={\n                previewState.isPreviewFullscreen ? \"Restore\" : \"Maximize\"\n              }\n            >\n              {previewState.isPreviewFullscreen ? (\n                <ArrowsInIcon className=\"size-4\" />\n              ) : (\n                <ArrowsOutIcon className=\"size-4\" />\n              )}\n            </WebPreviewNavigationButton>\n          </WebPreviewNavigation>\n\n          {previewHealth.state === \"error\" ? (\n            <div className=\"flex min-h-0 flex-1 items-center justify-center border-t px-6 py-10 text-center\">\n              <div className=\"max-w-xl space-y-2\">\n                <p className=\"font-medium text-foreground text-sm\">\n                  Preview is unavailable\n                </p>\n                <p className=\"text-muted-foreground text-sm\">\n                  {formatPreviewStatusSummary(\n                    previewHealth.details ??\n                      parsePreviewStatus({\n                        errorCode: \"UNKNOWN_PREVIEW_ERROR\",\n                        message: \"The preview URL could not be verified.\",\n                        ok: false,\n                        status: null,\n                        statusText: null,\n                      })\n                  )}\n                </p>\n              </div>\n            </div>\n          ) : (\n            <WebPreviewBody\n              className=\"min-h-0 flex-1\"\n              src={previewState.activePreviewUrl}\n            />\n          )}\n\n          <WebPreviewConsole logs={previewState.previewLogs} />\n        </WebPreview>\n      </div>\n    </TabsContent>\n  );\n}\n",
      "type": "registry:component",
      "target": "@components/sandbox-agent/sandbox-preview-pane.tsx"
    },
    {
      "path": "registry/sandbox-agent/components/sandbox-agent/sandbox-runtime-sidebar.tsx",
      "content": "\"use client\";\n\nimport { Badge } from \"@/components/ui/badge\";\n\nimport { configuredTools } from \"./sandbox-agent-model\";\n\ninterface SandboxRuntimeSidebarProps {\n  nodeVersion: string;\n  previewPort: number;\n  sandboxProvider: string;\n}\n\nexport function SandboxRuntimeSidebar({\n  nodeVersion,\n  previewPort,\n  sandboxProvider,\n}: SandboxRuntimeSidebarProps) {\n  return (\n    <aside className=\"border border-foreground/10 bg-background p-4\">\n      <div className=\"space-y-5\">\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          <p className=\"mt-1 text-muted-foreground text-sm\">\n            {sandboxProvider}\n          </p>\n        </div>\n\n        <div className=\"space-y-2\">\n          <p className=\"text-[11px] text-muted-foreground uppercase tracking-[0.2em]\">\n            Configured tools\n          </p>\n          {configuredTools.map((tool) => (\n            <div\n              className=\"space-y-1 border border-foreground/10 p-3\"\n              key={tool.name}\n            >\n              <p className=\"font-medium text-sm\">{tool.name}</p>\n              <p className=\"text-muted-foreground text-sm/relaxed\">\n                {tool.description}\n              </p>\n            </div>\n          ))}\n        </div>\n\n        <div>\n          <p className=\"text-[11px] text-muted-foreground uppercase tracking-[0.2em]\">\n            Preview contract\n          </p>\n          <div className=\"mt-2 flex flex-wrap gap-2\">\n            <Badge variant=\"secondary\">port {previewPort}</Badge>\n            <Badge variant=\"secondary\">HTML / CSS / JS</Badge>\n            <Badge variant=\"secondary\">live iframe</Badge>\n          </div>\n        </div>\n\n        <div>\n          <p className=\"text-[11px] text-muted-foreground uppercase tracking-[0.2em]\">\n            Source core\n          </p>\n          <p className=\"mt-1 text-sm\">\n            The model writes code in a persistent sandbox workspace, uses bash\n            and direct file tools for iteration, then calls{\" \"}\n            <code>startPreview</code> to publish the generated prototype.\n          </p>\n        </div>\n      </div>\n    </aside>\n  );\n}\n",
      "type": "registry:component",
      "target": "@components/sandbox-agent/sandbox-runtime-sidebar.tsx"
    },
    {
      "path": "registry/sandbox-agent/components/sandbox-agent/sandbox-agent-assistant-trace.tsx",
      "content": "\"use client\";\n\nimport { GlobeHemisphereWestIcon } from \"@phosphor-icons/react\";\nimport { MessageResponse } from \"@/components/ai-elements/message\";\nimport {\n  Reasoning,\n  ReasoningContent,\n  ReasoningTrigger,\n} from \"@/components/ai-elements/reasoning\";\nimport { Shimmer } from \"@/components/ai-elements/shimmer\";\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 type { UIMessage } from \"ai\";\nimport { getPreviewOutput, sanitizePreviewText } from \"./preview-state\";\nimport {\n  getReasoningText,\n  getTextContent,\n  getToolParts,\n  getWrittenFiles,\n} from \"./sandbox-agent-model\";\n\ninterface SandboxAgentAssistantTraceProps {\n  isLastMessage: boolean;\n  isStreaming: boolean;\n  message: UIMessage;\n  onOpenPreview: () => void;\n}\n\nexport function SandboxAgentAssistantTrace({\n  isLastMessage,\n  isStreaming,\n  message,\n  onOpenPreview,\n}: SandboxAgentAssistantTraceProps) {\n  const reasoningText = getReasoningText(message);\n  const toolParts = getToolParts(message);\n  const preview = getPreviewOutput(toolParts);\n  const text = sanitizePreviewText(\n    getTextContent(message),\n    preview?.url ?? null\n  );\n  const writtenFiles = getWrittenFiles(toolParts);\n  const hasReasoning = reasoningText.length > 0;\n  const hasText = text.length > 0;\n  const lastPart = message.parts.at(-1);\n  const isReasoningStreaming =\n    isLastMessage && isStreaming && lastPart?.type === \"reasoning\";\n  const showBodyThinking =\n    isLastMessage && isStreaming && !hasReasoning && !hasText;\n  let bodyContent: React.ReactNode = null;\n\n  if (hasText) {\n    bodyContent = <MessageResponse>{text}</MessageResponse>;\n  } else if (showBodyThinking) {\n    bodyContent = <Shimmer className=\"text-sm\">Thinking...</Shimmer>;\n  }\n\n  return (\n    <>\n      {hasReasoning ? (\n        <Reasoning className=\"w-full\" isStreaming={isReasoningStreaming}>\n          <ReasoningTrigger />\n          <ReasoningContent>{reasoningText}</ReasoningContent>\n        </Reasoning>\n      ) : null}\n\n      {writtenFiles.length > 0 ? (\n        <div className=\"space-y-2\">\n          <p className=\"text-[11px] text-muted-foreground uppercase tracking-[0.2em]\">\n            Generated files\n          </p>\n          <div className=\"flex flex-wrap gap-2\">\n            {writtenFiles.map((filePath) => (\n              <Badge key={filePath} variant=\"secondary\">\n                {filePath}\n              </Badge>\n            ))}\n          </div>\n        </div>\n      ) : null}\n\n      {toolParts.map((part) => (\n        <Tool key={part.toolCallId}>\n          {part.type === \"dynamic-tool\" ? (\n            <ToolHeader\n              state={part.state}\n              toolName={part.toolName}\n              type={part.type}\n            />\n          ) : (\n            <ToolHeader state={part.state} type={part.type} />\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      {preview?.url ? (\n        <div className=\"space-y-3\">\n          <div className=\"flex flex-wrap items-center gap-2\">\n            <Badge variant=\"outline\">{preview.directory ?? \".\"}</Badge>\n            <Badge variant=\"outline\">port {preview.port ?? 3000}</Badge>\n            {preview.entryPath ? (\n              <Badge variant=\"secondary\">{preview.entryPath}</Badge>\n            ) : null}\n          </div>\n\n          <Button\n            className=\"w-fit\"\n            onClick={onOpenPreview}\n            type=\"button\"\n            variant=\"outline\"\n          >\n            <GlobeHemisphereWestIcon className=\"size-4\" />\n            Open preview\n          </Button>\n        </div>\n      ) : null}\n\n      {bodyContent}\n    </>\n  );\n}\n",
      "type": "registry:component",
      "target": "@components/sandbox-agent/sandbox-agent-assistant-trace.tsx"
    },
    {
      "path": "registry/sandbox-agent/components/sandbox-agent/sandbox-agent-model.ts",
      "content": "\"use client\";\n\nimport type { ToolPart } from \"@/components/ai-elements/tool\";\nimport { isReasoningUIPart, isToolUIPart, type UIMessage } from \"ai\";\n\nexport const configuredTools = [\n  {\n    description: \"Run shell commands inside the sandbox workspace.\",\n    name: \"bash\",\n  },\n  {\n    description: \"Read sandbox-backed files directly.\",\n    name: \"readFile\",\n  },\n  {\n    description: \"Write sandbox-backed files directly.\",\n    name: \"writeFile\",\n  },\n  {\n    description: \"Expose the generated prototype through the preview URL.\",\n    name: \"startPreview\",\n  },\n] as const;\n\nexport const samplePrompts = [\n  \"Build a pricing landing page with an interactive calculator and start a live preview.\",\n  \"Create a waitlist landing page with a ROI calculator and expose the result through the sandbox preview.\",\n  \"Generate a product microsite with HTML, CSS, and JavaScript, then let me iterate on the copy and calculator logic.\",\n] as const;\n\nexport function getTextContent(message: UIMessage) {\n  return message.parts\n    .filter((part) => part.type === \"text\")\n    .map((part) => part.text)\n    .join(\"\\n\");\n}\n\nexport function getReasoningText(message: UIMessage) {\n  return message.parts\n    .filter(isReasoningUIPart)\n    .map((part) => part.text)\n    .join(\"\\n\\n\");\n}\n\nexport function getToolParts(message: UIMessage) {\n  return message.parts.filter(isToolUIPart);\n}\n\nexport function getToolName(part: ToolPart) {\n  return part.type === \"dynamic-tool\"\n    ? part.toolName\n    : part.type.split(\"-\").slice(1).join(\"-\");\n}\n\nexport function getWrittenFiles(toolParts: ToolPart[]) {\n  return toolParts\n    .filter(\n      (part) =>\n        getToolName(part) === \"writeFile\" && part.state === \"output-available\"\n    )\n    .map((part) => {\n      if (!part.output || typeof part.output !== \"object\") {\n        return null;\n      }\n\n      const candidate = \"path\" in part.output ? part.output.path : null;\n\n      return typeof candidate === \"string\" ? candidate : null;\n    })\n    .filter((path): path is string => Boolean(path));\n}\n",
      "type": "registry:component",
      "target": "@components/sandbox-agent/sandbox-agent-model.ts"
    },
    {
      "path": "registry/sandbox-agent/components/sandbox-agent/preview-state.ts",
      "content": "import {\n  type DynamicToolUIPart,\n  isToolUIPart,\n  type ToolUIPart,\n  type UIMessage,\n} from \"ai\";\n\nexport interface SandboxPreviewOutput {\n  directory: string | null;\n  entryPath: string | null;\n  port: number | null;\n  url: string;\n}\n\nexport interface SandboxPreviewStatus {\n  errorCode: string | null;\n  message: string | null;\n  ok: boolean;\n  status: number | null;\n  statusText: string | null;\n}\n\ntype PreviewToolPart = ToolUIPart | DynamicToolUIPart;\n\nfunction getToolName(part: PreviewToolPart) {\n  return part.type === \"dynamic-tool\"\n    ? part.toolName\n    : part.type.split(\"-\").slice(1).join(\"-\");\n}\n\nfunction readObjectField(value: unknown, field: string): string | null {\n  if (!value || typeof value !== \"object\" || !(field in value)) {\n    return null;\n  }\n\n  const candidate = value[field as keyof typeof value];\n\n  return typeof candidate === \"string\" ? candidate : null;\n}\n\nfunction readNumberField(value: unknown, field: string): number | null {\n  if (!value || typeof value !== \"object\" || !(field in value)) {\n    return null;\n  }\n\n  const candidate = value[field as keyof typeof value];\n\n  return typeof candidate === \"number\" ? candidate : null;\n}\n\nexport function getPreviewOutput(\n  toolParts: PreviewToolPart[]\n): SandboxPreviewOutput | null {\n  const previewPart = [...toolParts]\n    .reverse()\n    .find(\n      (part) =>\n        getToolName(part) === \"startPreview\" &&\n        part.state === \"output-available\" &&\n        readObjectField(part.output, \"url\")\n    );\n\n  if (!previewPart) {\n    return null;\n  }\n\n  const url = readObjectField(previewPart.output, \"url\");\n\n  if (!url) {\n    return null;\n  }\n\n  return {\n    directory: readObjectField(previewPart.output, \"directory\"),\n    entryPath: readObjectField(previewPart.output, \"entryPath\"),\n    port: readNumberField(previewPart.output, \"port\"),\n    url,\n  };\n}\n\nexport function getLatestPreviewOutput(\n  messages: UIMessage[]\n): SandboxPreviewOutput | null {\n  for (const message of [...messages].reverse()) {\n    const preview = getPreviewOutput(message.parts.filter(isToolUIPart));\n\n    if (preview) {\n      return preview;\n    }\n  }\n\n  return null;\n}\n\nexport function sanitizePreviewText(text: string, previewUrl: string | null) {\n  if (!(previewUrl && text.includes(previewUrl))) {\n    return text;\n  }\n\n  return text.split(previewUrl).join(\"the Preview tab\");\n}\n\nexport function parsePreviewStatus(value: unknown): SandboxPreviewStatus {\n  const objectValue =\n    value && typeof value === \"object\"\n      ? (value as Record<string, unknown>)\n      : ({} as Record<string, unknown>);\n\n  return {\n    errorCode:\n      typeof objectValue.errorCode === \"string\" ? objectValue.errorCode : null,\n    message:\n      typeof objectValue.message === \"string\" ? objectValue.message : null,\n    ok: objectValue.ok === true,\n    status: typeof objectValue.status === \"number\" ? objectValue.status : null,\n    statusText:\n      typeof objectValue.statusText === \"string\"\n        ? objectValue.statusText\n        : null,\n  };\n}\n\nexport function formatPreviewStatusSummary(status: SandboxPreviewStatus) {\n  if (status.ok) {\n    return \"Preview is reachable.\";\n  }\n\n  const parts = [\"Preview is unavailable.\"];\n\n  if (typeof status.status === \"number\") {\n    parts.push(`HTTP ${status.status}`);\n  }\n\n  if (status.errorCode) {\n    parts.push(status.errorCode);\n  }\n\n  if (status.message) {\n    parts.push(status.message);\n  }\n\n  return parts.join(\" \");\n}\n\nexport function appendPreviewHistory(\n  history: string[],\n  currentIndex: number,\n  url: string\n) {\n  const currentUrl = currentIndex >= 0 ? history[currentIndex] : null;\n\n  if (currentUrl === url) {\n    return {\n      history,\n      index: currentIndex,\n    };\n  }\n\n  const nextHistory = history.slice(0, currentIndex + 1);\n  nextHistory.push(url);\n\n  return {\n    history: nextHistory,\n    index: nextHistory.length - 1,\n  };\n}\n\nexport function movePreviewHistory(\n  history: string[],\n  currentIndex: number,\n  direction: -1 | 1\n) {\n  const nextIndex = currentIndex + direction;\n\n  if (nextIndex < 0 || nextIndex >= history.length) {\n    return null;\n  }\n\n  const nextUrl = history[nextIndex];\n\n  if (!nextUrl) {\n    return null;\n  }\n\n  return {\n    index: nextIndex,\n    url: nextUrl,\n  };\n}\n",
      "type": "registry:component",
      "target": "@components/sandbox-agent/preview-state.ts"
    },
    {
      "path": "registry/sandbox-agent/components/sandbox-agent/use-sandbox-agent-chat.ts",
      "content": "\"use client\";\n\nimport { Chat, useChat } from \"@ai-sdk/react\";\nimport { DefaultChatTransport } from \"ai\";\nimport { useState } from \"react\";\n\nexport function useSandboxAgentChat() {\n  const [chat] = useState(\n    () =>\n      new Chat({\n        transport: new DefaultChatTransport({\n          api: \"/api/demos/sandbox-agent\",\n        }),\n      })\n  );\n  const controller = useChat({ chat });\n  const hasMessages = controller.messages.length > 0;\n  const isBusy =\n    controller.status === \"submitted\" || controller.status === \"streaming\";\n\n  return {\n    ...controller,\n    chat,\n    hasMessages,\n    isBusy,\n  };\n}\n",
      "type": "registry:component",
      "target": "@components/sandbox-agent/use-sandbox-agent-chat.ts"
    },
    {
      "path": "registry/sandbox-agent/components/sandbox-agent/use-sandbox-agent-workspace-view-model.ts",
      "content": "\"use client\";\n\nimport { useEffect, useMemo, useState } from \"react\";\n\nimport { getLatestPreviewOutput } from \"./preview-state\";\nimport { samplePrompts } from \"./sandbox-agent-model\";\nimport { useSandboxAgentChat } from \"./use-sandbox-agent-chat\";\nimport { useSandboxPreviewState } from \"./use-sandbox-preview-state\";\n\nexport type SandboxAgentTab = \"conversation\" | \"preview\";\n\nexport function useSandboxAgentWorkspaceViewModel() {\n  const [activeTab, setActiveTab] = useState<SandboxAgentTab>(\"conversation\");\n  const [isTabsMounted, setIsTabsMounted] = useState(false);\n  const {\n    error,\n    hasMessages,\n    isBusy,\n    messages,\n    regenerate,\n    sendMessage,\n    status,\n    stop,\n  } = useSandboxAgentChat();\n  const latestPreview = useMemo(\n    () => getLatestPreviewOutput(messages),\n    [messages]\n  );\n  const previewState = useSandboxPreviewState(latestPreview);\n\n  useEffect(() => {\n    setIsTabsMounted(true);\n  }, []);\n\n  return {\n    activeTab,\n    error,\n    hasMessages,\n    isBusy,\n    latestPreview,\n    messages,\n    previewState,\n    samplePrompts,\n    setActiveTab,\n    status,\n    stop,\n    isTabsMounted,\n    actions: {\n      onOpenPreview: () => setActiveTab(\"preview\"),\n      onRegenerate: () => regenerate(),\n      onSendMessage: (text: string) => sendMessage({ text }),\n      onStop: stop,\n      onTabChange: (value: SandboxAgentTab) => setActiveTab(value),\n    },\n  };\n}\n",
      "type": "registry:component",
      "target": "@components/sandbox-agent/use-sandbox-agent-workspace-view-model.ts"
    },
    {
      "path": "registry/sandbox-agent/components/sandbox-agent/use-sandbox-preview-state.ts",
      "content": "\"use client\";\n\nimport { useCallback, useEffect, useRef, useState } from \"react\";\n\nimport {\n  appendPreviewHistory,\n  formatPreviewStatusSummary,\n  movePreviewHistory,\n  parsePreviewStatus,\n  type SandboxPreviewStatus,\n} from \"./preview-state\";\n\ninterface PreviewLog {\n  level: \"error\" | \"log\" | \"warn\";\n  message: string;\n  timestamp: Date;\n}\n\ninterface LatestPreview {\n  directory: string | null;\n  entryPath: string | null;\n  port: number | null;\n  url: string;\n}\n\nexport interface SandboxPreviewState {\n  activePreviewUrl: string;\n  appendPreviewLog: (level: PreviewLog[\"level\"], message: string) => void;\n  canGoBack: boolean;\n  canGoForward: boolean;\n  handleCopy: () => Promise<void>;\n  handleGoBack: () => void;\n  handleGoForward: () => void;\n  handleOpenInNewTab: () => void;\n  handlePreviewUrlChange: (nextUrl: string) => void;\n  handleReload: () => void;\n  handleToggleFullscreen: () => Promise<void>;\n  isPreviewFullscreen: boolean;\n  previewContainerRef: React.RefObject<HTMLDivElement | null>;\n  previewLogs: PreviewLog[];\n  previewReloadKey: number;\n}\n\nexport interface PreviewHealthState {\n  details: SandboxPreviewStatus | null;\n  state: \"checking\" | \"error\" | \"idle\" | \"ok\";\n}\n\nexport function useSandboxPreviewState(\n  latestPreview: LatestPreview | null\n): SandboxPreviewState {\n  const [isPreviewFullscreen, setIsPreviewFullscreen] = useState(false);\n  const [previewHistoryState, setPreviewHistoryState] = useState<{\n    history: string[];\n    index: number;\n  }>({\n    history: [],\n    index: -1,\n  });\n  const [previewLogs, setPreviewLogs] = useState<PreviewLog[]>([]);\n  const [previewReloadKey, setPreviewReloadKey] = useState(0);\n  const [previewUrl, setPreviewUrl] = useState(\"\");\n  const previewContainerRef = useRef<HTMLDivElement | null>(null);\n  const lastPreviewUrlRef = useRef<string | null>(null);\n  const activePreviewUrl = previewUrl || latestPreview?.url || \"\";\n  const canGoBack = previewHistoryState.index > 0;\n  const canGoForward =\n    previewHistoryState.index >= 0 &&\n    previewHistoryState.index < previewHistoryState.history.length - 1;\n\n  const appendPreviewLog = useCallback(\n    (level: PreviewLog[\"level\"], message: string) => {\n      setPreviewLogs((current) => [\n        ...current.slice(-49),\n        {\n          level,\n          message,\n          timestamp: new Date(),\n        },\n      ]);\n    },\n    []\n  );\n\n  const handlePreviewUrlChange = useCallback(\n    (nextUrl: string) => {\n      setPreviewUrl(nextUrl);\n      setPreviewHistoryState((current) =>\n        appendPreviewHistory(current.history, current.index, nextUrl)\n      );\n      appendPreviewLog(\"log\", `Navigated to ${nextUrl}`);\n    },\n    [appendPreviewLog]\n  );\n\n  const handleGoBack = useCallback(() => {\n    setPreviewHistoryState((current) => {\n      const previous = movePreviewHistory(current.history, current.index, -1);\n\n      if (!previous) {\n        return current;\n      }\n\n      setPreviewUrl(previous.url);\n      appendPreviewLog(\"log\", `Went back to ${previous.url}`);\n\n      return {\n        history: current.history,\n        index: previous.index,\n      };\n    });\n  }, [appendPreviewLog]);\n\n  const handleGoForward = useCallback(() => {\n    setPreviewHistoryState((current) => {\n      const next = movePreviewHistory(current.history, current.index, 1);\n\n      if (!next) {\n        return current;\n      }\n\n      setPreviewUrl(next.url);\n      appendPreviewLog(\"log\", `Went forward to ${next.url}`);\n\n      return {\n        history: current.history,\n        index: next.index,\n      };\n    });\n  }, [appendPreviewLog]);\n\n  const handleReload = useCallback(() => {\n    setPreviewReloadKey((current) => current + 1);\n\n    if (activePreviewUrl) {\n      appendPreviewLog(\"log\", `Reloaded ${activePreviewUrl}`);\n    }\n  }, [activePreviewUrl, appendPreviewLog]);\n\n  const handleCopy = useCallback(async () => {\n    if (!activePreviewUrl) {\n      appendPreviewLog(\"warn\", \"No preview URL is available yet.\");\n      return;\n    }\n\n    if (!navigator.clipboard) {\n      appendPreviewLog(\"error\", \"Clipboard API is not available.\");\n      return;\n    }\n\n    try {\n      await navigator.clipboard.writeText(activePreviewUrl);\n      appendPreviewLog(\"log\", `Copied ${activePreviewUrl} to the clipboard.`);\n    } catch (error) {\n      appendPreviewLog(\n        \"error\",\n        error instanceof Error\n          ? `Failed to copy preview URL: ${error.message}`\n          : \"Failed to copy preview URL.\"\n      );\n    }\n  }, [activePreviewUrl, appendPreviewLog]);\n\n  const handleOpenInNewTab = useCallback(() => {\n    if (!activePreviewUrl) {\n      appendPreviewLog(\"warn\", \"No preview URL is available yet.\");\n      return;\n    }\n\n    window.open(activePreviewUrl, \"_blank\", \"noopener,noreferrer\");\n    appendPreviewLog(\"log\", `Opened ${activePreviewUrl} in a new tab.`);\n  }, [activePreviewUrl, appendPreviewLog]);\n\n  const handleToggleFullscreen = useCallback(async () => {\n    const container = previewContainerRef.current;\n\n    if (!container) {\n      appendPreviewLog(\"warn\", \"Preview container is not available.\");\n      return;\n    }\n\n    if (document.fullscreenElement === container) {\n      await document.exitFullscreen();\n      appendPreviewLog(\"log\", \"Exited preview fullscreen.\");\n      return;\n    }\n\n    await container.requestFullscreen();\n    appendPreviewLog(\"log\", \"Entered preview fullscreen.\");\n  }, [appendPreviewLog]);\n\n  useEffect(() => {\n    const handleFullscreenChange = () => {\n      setIsPreviewFullscreen(\n        document.fullscreenElement === previewContainerRef.current\n      );\n    };\n\n    document.addEventListener(\"fullscreenchange\", handleFullscreenChange);\n\n    return () => {\n      document.removeEventListener(\"fullscreenchange\", handleFullscreenChange);\n    };\n  }, []);\n\n  useEffect(() => {\n    const nextUrl = latestPreview?.url ?? null;\n\n    if (!nextUrl || lastPreviewUrlRef.current === nextUrl) {\n      return;\n    }\n\n    lastPreviewUrlRef.current = nextUrl;\n    setPreviewUrl(nextUrl);\n    setPreviewHistoryState((current) =>\n      appendPreviewHistory(current.history, current.index, nextUrl)\n    );\n    appendPreviewLog(\"log\", `Preview ready at ${nextUrl}`);\n  }, [appendPreviewLog, latestPreview?.url]);\n\n  return {\n    activePreviewUrl,\n    appendPreviewLog,\n    canGoBack,\n    canGoForward,\n    handleCopy,\n    handleGoBack,\n    handleGoForward,\n    handleOpenInNewTab,\n    handlePreviewUrlChange,\n    handleReload,\n    handleToggleFullscreen,\n    isPreviewFullscreen,\n    previewContainerRef,\n    previewLogs,\n    previewReloadKey,\n  };\n}\n\nexport function usePreviewHealth(\n  previewUrl: string,\n  previewReloadKey: number,\n  appendPreviewLog: SandboxPreviewState[\"appendPreviewLog\"]\n) {\n  const [health, setHealth] = useState<PreviewHealthState>({\n    details: null,\n    state: \"idle\",\n  });\n\n  useEffect(() => {\n    if (!previewUrl) {\n      setHealth({\n        details: null,\n        state: \"idle\",\n      });\n      return;\n    }\n\n    let isCancelled = false;\n    const controller = new AbortController();\n\n    setHealth((current) => ({\n      details: current.details,\n      state: \"checking\",\n    }));\n\n    fetch(\n      `/api/demos/sandbox-agent/preview-status?url=${encodeURIComponent(\n        previewUrl\n      )}&reload=${previewReloadKey}`,\n      {\n        cache: \"no-store\",\n        signal: controller.signal,\n      }\n    )\n      .then(async (response) => {\n        const payload = parsePreviewStatus(await response.json());\n\n        if (isCancelled) {\n          return;\n        }\n\n        setHealth({\n          details: payload,\n          state: payload.ok ? \"ok\" : \"error\",\n        });\n\n        if (payload.ok) {\n          appendPreviewLog(\"log\", `Preview check succeeded for ${previewUrl}`);\n          return;\n        }\n\n        appendPreviewLog(\"error\", formatPreviewStatusSummary(payload));\n      })\n      .catch((error) => {\n        if (isCancelled || controller.signal.aborted) {\n          return;\n        }\n\n        const details = parsePreviewStatus({\n          errorCode: \"FETCH_FAILED\",\n          message:\n            error instanceof Error\n              ? error.message\n              : \"Failed to check preview URL.\",\n          ok: false,\n          status: null,\n          statusText: null,\n        });\n\n        setHealth({\n          details,\n          state: \"error\",\n        });\n        appendPreviewLog(\"error\", formatPreviewStatusSummary(details));\n      });\n\n    return () => {\n      isCancelled = true;\n      controller.abort();\n    };\n  }, [appendPreviewLog, previewReloadKey, previewUrl]);\n\n  return health;\n}\n",
      "type": "registry:component",
      "target": "@components/sandbox-agent/use-sandbox-preview-state.ts"
    },
    {
      "path": "registry/sandbox-agent/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/sandbox-agent/lib/sandbox-agent/server/chat.ts",
      "content": "import {\n  createAgentUIStreamResponse,\n  stepCountIs,\n  ToolLoopAgent,\n  type UIMessage,\n} from \"ai\";\n\nimport {\n  resolveSandboxAgentChatModel,\n  SANDBOX_AGENT_PROVIDER_OPTIONS,\n} from \"./model\";\nimport { createSandboxAgentGateway, getSandboxAgentEnv } from \"./env\";\nimport { createSandboxAgentWorkspace } from \"./workspace\";\n\nexport const sandboxAgentInstructions = [\n  \"You are the sandbox-agent demo for a product and engineering team.\",\n  \"Work like a focused frontend prototyper inside the sandbox workspace.\",\n  \"Prefer plain HTML, CSS, and JavaScript unless the user asks for something heavier.\",\n  \"Use writeFile and readFile for direct file editing, and use bash for scaffold, inspection, and iterative code changes.\",\n  \"After the prototype is previewable, call startPreview so the workspace can show the live result.\",\n  \"Keep the final answer concise and report the main files created or updated.\",\n].join(\" \");\n\nexport async function streamSandboxAgent(\n  messages: UIMessage[],\n  {\n    env = getSandboxAgentEnv(),\n    localPreviewBaseUrl,\n    sessionId,\n    previewPort,\n  }: {\n    env?: ReturnType<typeof getSandboxAgentEnv>;\n    localPreviewBaseUrl?: string;\n    previewPort?: number;\n    sessionId: string;\n  }\n) {\n  const gateway = createSandboxAgentGateway(env);\n  const chatModel = resolveSandboxAgentChatModel(env);\n  const workspace = await createSandboxAgentWorkspace({\n    env,\n    localPreviewBaseUrl,\n    sessionId,\n  });\n  const agent = new ToolLoopAgent({\n    instructions: sandboxAgentInstructions,\n    model: gateway(chatModel),\n    prepareCall: ({ ...call }) => ({\n      ...call,\n      instructions: [\n        sandboxAgentInstructions,\n        `Sandbox project root: ${workspace.projectRoot}`,\n        `Default artifacts root: ${workspace.artifactsRoot}`,\n        `Preview port: ${previewPort ?? workspace.previewPort}`,\n        `Suggested use cases:\\n${workspace.suggestedUseCasesText}`,\n      ].join(\"\\n\\n\"),\n    }),\n    providerOptions: SANDBOX_AGENT_PROVIDER_OPTIONS,\n    stopWhen: stepCountIs(8),\n    tools: workspace.toolset.tools,\n  });\n\n  return await createAgentUIStreamResponse({\n    agent,\n    sendReasoning: true,\n    uiMessages: messages,\n  });\n}\n",
      "type": "registry:lib",
      "target": "@lib/sandbox-agent/server/chat.ts"
    },
    {
      "path": "registry/sandbox-agent/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/sandbox-agent/lib/sandbox-agent/server/env-source.ts",
      "content": "export function getSandboxAgentAppEnv() {\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/sandbox-agent/server/env-source.ts"
    },
    {
      "path": "registry/sandbox-agent/lib/sandbox-agent/server/env.ts",
      "content": "import { getSandboxAgentAppEnv } from \"./env-source\";\nimport {\n  buildAiGatewayContractSetupState,\n  createAiGatewayFromContract,\n  readAiGatewayContractConfig,\n  type AiGatewayContractConfig,\n  type AiGatewayContractSetupState,\n  type AiGatewayEnvRecord,\n  type AiGatewaySetupConfig,\n} from \"@/lib/ai-gateway/contract\";\n\nexport const DEFAULT_SANDBOX_AGENT_CHAT_MODEL = \"openai/gpt-5-mini\";\n\nexport type SandboxAgentEnv = AiGatewayEnvRecord;\n\nexport type SandboxAgentConfig = AiGatewayContractConfig;\n\nexport interface SandboxAgentSandboxTokenCredentials {\n  projectId: string;\n  teamId: string;\n  token: string;\n}\n\nexport interface SandboxAgentSandboxSetupState {\n  authMode: \"local\" | \"oidc\" | \"token\";\n  isReady: boolean;\n  issues: string[];\n  providerLabel: \"Local sandbox\" | \"Vercel Sandbox\";\n  runtime: \"node24\";\n}\n\nexport interface SandboxAgentSetupState\n  extends AiGatewayContractSetupState<AiGatewaySetupConfig> {\n  sandboxProvider: SandboxAgentSandboxSetupState[\"providerLabel\"];\n}\n\nexport type SandboxAgentGateway = ReturnType<typeof createAiGatewayFromContract>;\n\nconst sandboxAgentContract = {\n  defaultChatModel: DEFAULT_SANDBOX_AGENT_CHAT_MODEL,\n  missingApiKeyError:\n    \"Missing AI_GATEWAY_API_KEY. Add it to .env.local before using the sandbox agent.\",\n  missingApiKeyIssue: \"AI_GATEWAY_API_KEY is missing. The demo can render, but sandbox chat requests will fail until it is configured.\",\n} as const;\n\nexport function getSandboxAgentEnv(): SandboxAgentEnv {\n  return getSandboxAgentAppEnv();\n}\n\nexport function getSandboxAgentConfig(\n  env: SandboxAgentEnv = getSandboxAgentEnv()\n): SandboxAgentConfig {\n  return readAiGatewayContractConfig(env, sandboxAgentContract);\n}\n\nexport function hasSandboxAgentSandboxTokenCredentials(env: SandboxAgentEnv) {\n  return Boolean(\n    env.VERCEL_PROJECT_ID && env.VERCEL_TEAM_ID && env.VERCEL_TOKEN\n  );\n}\n\nexport function getSandboxAgentSandboxTokenCredentials(\n  env: SandboxAgentEnv = getSandboxAgentEnv()\n): SandboxAgentSandboxTokenCredentials {\n  const { VERCEL_PROJECT_ID, VERCEL_TEAM_ID, VERCEL_TOKEN } = env;\n\n  if (!(VERCEL_PROJECT_ID && VERCEL_TEAM_ID && VERCEL_TOKEN)) {\n    throw new Error(\n      \"Vercel Sandbox token credentials are incomplete. Expected VERCEL_TOKEN, VERCEL_TEAM_ID, and VERCEL_PROJECT_ID.\"\n    );\n  }\n\n  return {\n    projectId: VERCEL_PROJECT_ID,\n    teamId: VERCEL_TEAM_ID,\n    token: VERCEL_TOKEN,\n  };\n}\n\nexport function getSandboxAgentSandboxSetupState(\n  env: SandboxAgentEnv = getSandboxAgentEnv()\n): SandboxAgentSandboxSetupState {\n  const issues: string[] = [];\n  const hasOidc = Boolean(env.VERCEL_OIDC_TOKEN);\n  const hasAnyTokenCredential = Boolean(\n    env.VERCEL_PROJECT_ID || env.VERCEL_TEAM_ID || env.VERCEL_TOKEN\n  );\n  let authMode: SandboxAgentSandboxSetupState[\"authMode\"] = \"local\";\n\n  if (hasOidc) {\n    authMode = \"oidc\";\n  } else if (hasSandboxAgentSandboxTokenCredentials(env)) {\n    authMode = \"token\";\n  }\n\n  if (\n    authMode === \"local\" &&\n    hasAnyTokenCredential &&\n    !hasSandboxAgentSandboxTokenCredentials(env)\n  ) {\n    issues.push(\n      \"Vercel Sandbox token credentials are incomplete. Set VERCEL_TOKEN, VERCEL_TEAM_ID, and VERCEL_PROJECT_ID together, or remove them to use local sandbox mode.\"\n    );\n  }\n\n  return {\n    authMode,\n    isReady: issues.length === 0,\n    issues,\n    providerLabel: authMode === \"local\" ? \"Local sandbox\" : \"Vercel Sandbox\",\n    runtime: \"node24\",\n  };\n}\n\nexport function getSandboxAgentSetupState(\n  env: SandboxAgentEnv = getSandboxAgentEnv()\n): SandboxAgentSetupState {\n  const sandboxSetup = getSandboxAgentSandboxSetupState(env);\n  const gatewaySetup = buildAiGatewayContractSetupState(env, {\n    ...sandboxAgentContract,\n    getAdditionalIssues: () => sandboxSetup.issues,\n  });\n\n  return {\n    ...gatewaySetup,\n    sandboxProvider: sandboxSetup.providerLabel,\n  };\n}\n\nexport function createSandboxAgentGateway(\n  env: SandboxAgentEnv = getSandboxAgentEnv()\n): SandboxAgentGateway {\n  return createAiGatewayFromContract(env, sandboxAgentContract);\n}\n",
      "type": "registry:lib",
      "target": "@lib/sandbox-agent/server/env.ts"
    },
    {
      "path": "registry/sandbox-agent/lib/sandbox-agent/server/local-sandbox.ts",
      "content": "import { spawn } from \"node:child_process\";\nimport { mkdir, readFile, rm, stat, writeFile } from \"node:fs/promises\";\nimport { tmpdir } from \"node:os\";\nimport path from \"node:path\";\n\nimport {\n  SANDBOX_PROJECT_ROOT,\n  type SandboxAgentSessionRegistry,\n} from \"./session\";\n\nconst localSandboxRoot = path.join(tmpdir(), \"ai-sdk-sandbox-agent\");\nconst commandTimeoutMs = 30_000;\nconst maxOutputBytes = 64 * 1024;\n\ninterface LocalSandboxAgentSessionRegistryOptions {\n  localPreviewBaseUrl?: string;\n  previewPort: number;\n}\n\nfunction sanitizeSessionId(sessionId: string) {\n  return sessionId.replace(/[^a-zA-Z0-9_-]/g, \"_\").slice(0, 96);\n}\n\nfunction getSessionRoot(sessionId: string) {\n  return path.join(localSandboxRoot, sanitizeSessionId(sessionId));\n}\n\nfunction isInsideDirectory(parent: string, child: string) {\n  const relativePath = path.relative(parent, child);\n\n  return (\n    relativePath === \"\" ||\n    (!relativePath.startsWith(\"..\") && !path.isAbsolute(relativePath))\n  );\n}\n\nfunction toLocalRelativePath(targetPath: string) {\n  if (targetPath.startsWith(SANDBOX_PROJECT_ROOT)) {\n    return path.relative(SANDBOX_PROJECT_ROOT, targetPath) || \".\";\n  }\n\n  if (path.isAbsolute(targetPath)) {\n    return targetPath.slice(1);\n  }\n\n  return targetPath;\n}\n\nfunction resolveLocalPath(projectRoot: string, targetPath: string) {\n  const resolvedPath = path.resolve(projectRoot, toLocalRelativePath(targetPath));\n\n  if (!isInsideDirectory(projectRoot, resolvedPath)) {\n    throw new Error(`Sandbox path escapes the local workspace: ${targetPath}`);\n  }\n\n  return resolvedPath;\n}\n\nasync function pathExists(localPath: string) {\n  try {\n    await stat(localPath);\n    return true;\n  } catch (error) {\n    if (\n      typeof error === \"object\" &&\n      error !== null &&\n      \"code\" in error &&\n      error.code === \"ENOENT\"\n    ) {\n      return false;\n    }\n\n    throw error;\n  }\n}\n\nfunction appendBounded(current: string, chunk: Buffer) {\n  const next = current + chunk.toString(\"utf-8\");\n\n  return next.length > maxOutputBytes ? next.slice(-maxOutputBytes) : next;\n}\n\nfunction runLocalCommand(command: string, cwd: string) {\n  return new Promise<{\n    command: string;\n    exitCode: number;\n    stderr: string;\n    stdout: string;\n  }>((resolve, reject) => {\n    let stdout = \"\";\n    let stderr = \"\";\n    let timedOut = false;\n    const child = spawn(\"bash\", [\"-lc\", command], {\n      cwd,\n      env: process.env,\n    });\n    const timeout = setTimeout(() => {\n      timedOut = true;\n      child.kill(\"SIGTERM\");\n    }, commandTimeoutMs);\n\n    child.stdout.on(\"data\", (chunk: Buffer) => {\n      stdout = appendBounded(stdout, chunk);\n    });\n    child.stderr.on(\"data\", (chunk: Buffer) => {\n      stderr = appendBounded(stderr, chunk);\n    });\n    child.on(\"error\", (error) => {\n      clearTimeout(timeout);\n      reject(error);\n    });\n    child.on(\"close\", (code) => {\n      clearTimeout(timeout);\n      resolve({\n        command,\n        exitCode: timedOut ? 124 : (code ?? 1),\n        stderr: timedOut\n          ? [stderr, `Command timed out after ${commandTimeoutMs}ms.`]\n              .filter(Boolean)\n              .join(\"\\n\")\n          : stderr,\n        stdout,\n      });\n    });\n  });\n}\n\nfunction normalizeEntryPath(entryPath: string) {\n  return entryPath.startsWith(\"/\") ? entryPath : `/${entryPath}`;\n}\n\nfunction buildLocalPreviewUrl(input: {\n  directory: string;\n  entryPath: string;\n  localPreviewBaseUrl: string;\n  sessionId: string;\n}) {\n  const normalizedDirectory =\n    input.directory === \".\" ? \"\" : input.directory.replace(/^\\/+/, \"\");\n  const previewPath = path.posix.join(\n    normalizedDirectory,\n    normalizeEntryPath(input.entryPath).slice(1)\n  );\n  const url = new URL(\n    [\n      \"/api/demos/sandbox-agent/local-preview\",\n      encodeURIComponent(input.sessionId),\n      ...previewPath.split(\"/\").filter(Boolean).map(encodeURIComponent),\n    ].join(\"/\"),\n    input.localPreviewBaseUrl\n  );\n\n  return url.toString();\n}\n\nexport function createLocalSandboxAgentSessionRegistry({\n  localPreviewBaseUrl = \"http://127.0.0.1:3000\",\n  previewPort,\n}: LocalSandboxAgentSessionRegistryOptions): SandboxAgentSessionRegistry {\n  return {\n    getDomain() {\n      return localPreviewBaseUrl;\n    },\n    getSession(sessionId) {\n      const sessionRoot = getSessionRoot(sessionId);\n      const projectRoot = path.join(sessionRoot, \"project\");\n      const artifactsRoot = path.join(projectRoot, \"artifacts\");\n\n      return {\n        artifactsRoot,\n        projectRoot,\n        sessionId,\n        async pathExists(targetPath) {\n          return pathExists(resolveLocalPath(projectRoot, targetPath));\n        },\n        async readFile(targetPath) {\n          return readFile(resolveLocalPath(projectRoot, targetPath), \"utf-8\");\n        },\n        async runCommand(command) {\n          await mkdir(projectRoot, { recursive: true });\n          await mkdir(artifactsRoot, { recursive: true });\n\n          return runLocalCommand(command, projectRoot);\n        },\n        async startPreview({\n          directory = \".\",\n          entryPath = \"/index.html\",\n          port = previewPort,\n        } = {}) {\n          const previewDirectory = resolveLocalPath(projectRoot, directory);\n\n          if (!(await pathExists(previewDirectory))) {\n            throw new Error(\n              `Preview directory \"${directory}\" does not exist in the local sandbox workspace.`\n            );\n          }\n\n          const normalizedEntryPath = normalizeEntryPath(entryPath);\n          const preview = {\n            directory,\n            entryPath: normalizedEntryPath,\n            port,\n            url: buildLocalPreviewUrl({\n              directory,\n              entryPath: normalizedEntryPath,\n              localPreviewBaseUrl,\n              sessionId,\n            }),\n          };\n\n          return preview;\n        },\n        async stop() {\n          await rm(sessionRoot, { force: true, recursive: true });\n        },\n        async writeFile(targetPath, content) {\n          const resolvedPath = resolveLocalPath(projectRoot, targetPath);\n\n          await mkdir(path.dirname(resolvedPath), { recursive: true });\n          await writeFile(resolvedPath, content, \"utf-8\");\n\n          return {\n            bytes: Buffer.byteLength(content, \"utf-8\"),\n            path: resolvedPath,\n          };\n        },\n      };\n    },\n    async stopSession(sessionId) {\n      await rm(getSessionRoot(sessionId), { force: true, recursive: true });\n    },\n  };\n}\n",
      "type": "registry:lib",
      "target": "@lib/sandbox-agent/server/local-sandbox.ts"
    },
    {
      "path": "registry/sandbox-agent/lib/sandbox-agent/server/model.ts",
      "content": "import {\n  DEFAULT_SANDBOX_AGENT_CHAT_MODEL,\n  getSandboxAgentEnv,\n  type SandboxAgentEnv,\n} from \"./env\";\n\nexport const SANDBOX_AGENT_PROVIDER_OPTIONS = {\n  openai: {\n    reasoningEffort: \"medium\",\n    reasoningSummary: \"auto\",\n  },\n} as const;\n\nexport function resolveSandboxAgentChatModel(\n  env: SandboxAgentEnv = getSandboxAgentEnv()\n): string {\n  return env.AI_GATEWAY_CHAT_MODEL || DEFAULT_SANDBOX_AGENT_CHAT_MODEL;\n}\n",
      "type": "registry:lib",
      "target": "@lib/sandbox-agent/server/model.ts"
    },
    {
      "path": "registry/sandbox-agent/lib/sandbox-agent/server/official-tools.ts",
      "content": "import { type ToolSet, tool } from \"ai\";\nimport { z } from \"zod\";\n\nimport type { SandboxAgentSession } from \"./session\";\nimport { SANDBOX_AGENT_PREVIEW_PORT, SANDBOX_PROJECT_ROOT } from \"./session\";\n\nexport interface SandboxAgentToolset {\n  tools: ToolSet;\n}\n\nexport async function createSandboxAgentToolset({\n  projectRoot = SANDBOX_PROJECT_ROOT,\n  session,\n}: {\n  projectRoot?: string;\n  session: SandboxAgentSession;\n}): Promise<SandboxAgentToolset> {\n  return {\n    tools: {\n      bash: tool({\n        description: `Run a shell command inside the sandbox workspace at ${projectRoot}.`,\n        execute: ({ command }) => session.runCommand(command),\n        inputSchema: z.object({\n          command: z\n            .string()\n            .min(1)\n            .describe(\"Shell command to run from the sandbox project root.\"),\n        }),\n      }),\n      readFile: tool({\n        description: \"Read a UTF-8 file from the sandbox workspace.\",\n        execute: async ({ path }) => ({\n          content: await session.readFile(path),\n          path,\n        }),\n        inputSchema: z.object({\n          path: z\n            .string()\n            .min(1)\n            .describe(\n              \"File path to read, relative to the sandbox project root unless absolute.\"\n            ),\n        }),\n      }),\n      writeFile: tool({\n        description: \"Write a UTF-8 file into the sandbox workspace.\",\n        execute: ({ content, path }) => session.writeFile(path, content),\n        inputSchema: z.object({\n          content: z.string().describe(\"UTF-8 file content to write.\"),\n          path: z\n            .string()\n            .min(1)\n            .describe(\n              \"File path to write, relative to the sandbox project root unless absolute.\"\n            ),\n        }),\n      }),\n      startPreview: tool({\n        description:\n          \"Expose a static HTML, CSS, and JavaScript prototype through the sandbox preview URL.\",\n        execute: ({ directory, entryPath, port }) =>\n          session.startPreview({\n            directory,\n            entryPath,\n            port,\n          }),\n        inputSchema: z.object({\n          directory: z\n            .string()\n            .default(\".\")\n            .describe(\n              \"Directory to serve, relative to the sandbox project root.\"\n            ),\n          entryPath: z\n            .string()\n            .default(\"/index.html\")\n            .describe(\"Preview path to open after the server starts.\"),\n          port: z\n            .number()\n            .int()\n            .min(SANDBOX_AGENT_PREVIEW_PORT)\n            .max(SANDBOX_AGENT_PREVIEW_PORT)\n            .default(SANDBOX_AGENT_PREVIEW_PORT)\n            .describe(\"Sandbox preview port. Use the default preview port.\"),\n        }),\n      }),\n    },\n  };\n}\n",
      "type": "registry:lib",
      "target": "@lib/sandbox-agent/server/official-tools.ts"
    },
    {
      "path": "registry/sandbox-agent/lib/sandbox-agent/server/runtime.ts",
      "content": "import { getSandboxAgentEnv, getSandboxAgentSetupState } from \"./env\";\nimport { resolveSandboxAgentChatModel } from \"./model\";\nimport { SANDBOX_AGENT_PREVIEW_PORT } from \"./session\";\n\nexport interface SandboxAgentRuntimeState {\n  chatModel: string;\n  isChatAvailable: boolean;\n  nodeVersion: string;\n  previewPort: number;\n  sandboxProvider: string;\n  setupMessage: string | null;\n  statusLabel: \"Ready\" | \"Setup required\";\n}\n\nexport function getSandboxAgentRuntimeState(\n  env = getSandboxAgentEnv()\n): SandboxAgentRuntimeState {\n  const setupState = getSandboxAgentSetupState(env);\n\n  return {\n    chatModel: resolveSandboxAgentChatModel(env),\n    isChatAvailable: setupState.isReady,\n    nodeVersion: setupState.nodeVersion,\n    previewPort: SANDBOX_AGENT_PREVIEW_PORT,\n    sandboxProvider: setupState.sandboxProvider,\n    setupMessage: setupState.issues.length > 0 ? setupState.issues.join(\" \") : null,\n    statusLabel: setupState.isReady ? \"Ready\" : \"Setup required\",\n  };\n}\n",
      "type": "registry:lib",
      "target": "@lib/sandbox-agent/server/runtime.ts"
    },
    {
      "path": "registry/sandbox-agent/lib/sandbox-agent/server/request.ts",
      "content": "import { type UIMessage, validateUIMessages } from \"ai\";\n\nimport { getSandboxAgentEnv } from \"./env\";\nimport { getSandboxAgentRuntimeState } from \"./runtime\";\n\ninterface SandboxAgentRequestBody {\n  id?: string;\n  messages?: UIMessage[];\n}\n\ninterface StreamSandboxAgentOptions {\n  localPreviewBaseUrl: string;\n  previewPort: number;\n  sessionId: string;\n}\n\ninterface SandboxAgentRequestDependencies {\n  streamSandboxAgent: (\n    messages: UIMessage[],\n    options: StreamSandboxAgentOptions\n  ) => Promise<Response> | Response;\n}\n\nconst invalidUiMessagesError =\n  'Expected each \"messages\" entry to match the UIMessage format.';\nconst invalidChatIdError =\n  'Expected a JSON body with an \"id\" string and a \"messages\" array.';\n\nasync function readSandboxAgentRequest(\n  body: unknown\n): Promise<{ messages: UIMessage[]; sessionId: string }> {\n  const { id, messages } = (body ?? {}) as SandboxAgentRequestBody;\n\n  if (!(typeof id === \"string\" && id.length > 0 && Array.isArray(messages))) {\n    throw new Error(invalidChatIdError);\n  }\n\n  try {\n    return {\n      messages: await validateUIMessages({ messages }),\n      sessionId: id,\n    };\n  } catch {\n    throw new Error(invalidUiMessagesError);\n  }\n}\n\nexport async function handleSandboxAgentRequest(\n  request: Request,\n  env = getSandboxAgentEnv(),\n  dependencies: Partial<SandboxAgentRequestDependencies> = {\n    streamSandboxAgent: async (messages, options) => {\n      const { streamSandboxAgent } = await import(\"./chat\");\n\n      return streamSandboxAgent(messages, {\n        env,\n        localPreviewBaseUrl: options.localPreviewBaseUrl,\n        previewPort: options.previewPort,\n        sessionId: options.sessionId,\n      });\n    },\n  }\n) {\n  const streamAgent =\n    dependencies.streamSandboxAgent ??\n    (async (messages: UIMessage[], options: StreamSandboxAgentOptions) => {\n      const { streamSandboxAgent } = await import(\"./chat\");\n\n      return streamSandboxAgent(messages, {\n        env,\n        localPreviewBaseUrl: options.localPreviewBaseUrl,\n        previewPort: options.previewPort,\n        sessionId: options.sessionId,\n      });\n    });\n  const runtimeState = getSandboxAgentRuntimeState(env);\n\n  if (!runtimeState.isChatAvailable) {\n    return Response.json(\n      {\n        error: runtimeState.setupMessage,\n      },\n      { status: 500 }\n    );\n  }\n\n  try {\n    const { messages, sessionId } = await readSandboxAgentRequest(\n      await request.json()\n    );\n\n    return streamAgent(messages, {\n      localPreviewBaseUrl: new URL(request.url).origin,\n      previewPort: runtimeState.previewPort,\n      sessionId,\n    });\n  } catch (error) {\n    if (\n      error instanceof Error &&\n      [invalidUiMessagesError, invalidChatIdError].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",
      "type": "registry:lib",
      "target": "@lib/sandbox-agent/server/request.ts"
    },
    {
      "path": "registry/sandbox-agent/lib/sandbox-agent/server/session.ts",
      "content": "import {\n  getSandboxAgentEnv,\n  getSandboxAgentSandboxSetupState,\n  type SandboxAgentEnv,\n} from \"./env\";\nimport type { VercelSandboxHandle } from \"./vercel-sandbox\";\n\nexport const SANDBOX_PROJECT_ROOT = \"/vercel/sandbox/project\";\nexport const SANDBOX_ARTIFACTS_ROOT = `${SANDBOX_PROJECT_ROOT}/artifacts`;\n\nexport const SANDBOX_AGENT_PREVIEW_PORT = 3000;\nexport const SANDBOX_AGENT_PREVIEW_SERVER_PATH =\n  \".sandbox-agent-preview-server.mjs\";\n\nconst SANDBOX_AGENT_PREVIEW_SERVER_SOURCE = String.raw`\nimport { createReadStream } from \"node:fs\";\nimport { access, stat } from \"node:fs/promises\";\nimport { createServer } from \"node:http\";\nimport path from \"node:path\";\n\nconst args = process.argv.slice(2);\nconst getArgValue = (flag, fallback) => {\n  const index = args.indexOf(flag);\n  return index >= 0 && args[index + 1] ? args[index + 1] : fallback;\n};\n\nconst root = path.resolve(getArgValue(\"--root\", \".\"));\nconst port = Number.parseInt(getArgValue(\"--port\", \"3000\"), 10);\n\nconst contentTypes = new Map([\n  [\".css\", \"text/css; charset=utf-8\"],\n  [\".html\", \"text/html; charset=utf-8\"],\n  [\".js\", \"text/javascript; charset=utf-8\"],\n  [\".json\", \"application/json; charset=utf-8\"],\n  [\".mjs\", \"text/javascript; charset=utf-8\"],\n  [\".png\", \"image/png\"],\n  [\".svg\", \"image/svg+xml\"],\n  [\".ts\", \"text/plain; charset=utf-8\"],\n]);\n\nfunction getContentType(targetPath) {\n  return contentTypes.get(path.extname(targetPath)) ?? \"text/plain; charset=utf-8\";\n}\n\ncreateServer(async (request, response) => {\n  const requestUrl = new URL(request.url ?? \"/\", \"http://127.0.0.1\");\n  const relativePath = decodeURIComponent(requestUrl.pathname);\n  const normalizedPath = path\n    .normalize(relativePath)\n    .replace(/^(\\.\\.(\\/|\\\\\\\\|$))+/, \"\");\n  const resolvedPath = path.join(\n    root,\n    normalizedPath === \"/\" ? \"index.html\" : normalizedPath\n  );\n\n  try {\n    await access(resolvedPath);\n    const fileStats = await stat(resolvedPath);\n\n    if (fileStats.isDirectory()) {\n      const directoryIndex = path.join(resolvedPath, \"index.html\");\n      await access(directoryIndex);\n      response.setHeader(\"Content-Type\", \"text/html; charset=utf-8\");\n      createReadStream(directoryIndex).pipe(response);\n      return;\n    }\n\n    response.setHeader(\"Content-Type\", getContentType(resolvedPath));\n    createReadStream(resolvedPath).pipe(response);\n  } catch {\n    response.statusCode = 404;\n    response.setHeader(\"Content-Type\", \"text/plain; charset=utf-8\");\n    response.end(\"Not found\");\n  }\n}).listen(port, \"0.0.0.0\");\n`;\n\ntype NamedSandboxHandle = VercelSandboxHandle & {\n  domain: (port: number) => string;\n  runCommand(options: {\n    args: string[];\n    cmd: string;\n    cwd: string;\n    detached?: boolean;\n  }): Promise<unknown>;\n};\n\ninterface BaseSandboxSession {\n  readonly artifactsRoot: string;\n  pathExists(targetPath: string): Promise<boolean>;\n  readonly projectRoot: string;\n  readFile(targetPath: string): Promise<string>;\n  runCommand(command: string): Promise<{\n    command: string;\n    exitCode: number;\n    stderr: string;\n    stdout: string;\n  }>;\n  readonly sessionId: string;\n  stop(): Promise<void>;\n  writeFile(\n    targetPath: string,\n    content: string\n  ): Promise<{\n    bytes: number;\n    path: string;\n  }>;\n}\n\ninterface BaseSandboxSessionRegistry {\n  getSession(sessionId: string): BaseSandboxSession;\n  stopSession(sessionId: string): Promise<void>;\n}\n\ninterface DetachedPreviewSandboxHandle {\n  runCommand(options: {\n    args: string[];\n    cmd: string;\n    cwd: string;\n    detached: true;\n  }): Promise<unknown>;\n}\n\nexport interface SandboxAgentPreview {\n  directory: string;\n  entryPath: string;\n  port: number;\n  url: string;\n}\n\nexport interface SandboxAgentSession {\n  readonly artifactsRoot: string;\n  pathExists(targetPath: string): Promise<boolean>;\n  readonly projectRoot: string;\n  readFile(targetPath: string): Promise<string>;\n  runCommand(command: string): Promise<{\n    command: string;\n    exitCode: number;\n    stderr: string;\n    stdout: string;\n  }>;\n  readonly sessionId: string;\n  startPreview(options?: {\n    directory?: string;\n    entryPath?: string;\n    port?: number;\n  }): Promise<SandboxAgentPreview>;\n  stop(): Promise<void>;\n  writeFile(\n    targetPath: string,\n    content: string\n  ): Promise<{\n    bytes: number;\n    path: string;\n  }>;\n}\n\nexport interface SandboxAgentSessionRegistry {\n  getDomain(sessionId: string, port: number): string;\n  getSession(sessionId: string): SandboxAgentSession;\n  stopSession(sessionId: string): Promise<void>;\n}\n\ninterface SandboxAgentSessionRegistryOptions {\n  localPreviewBaseUrl?: string;\n}\n\nfunction quoteShell(value: string) {\n  return `'${value.replace(/'/g, `'\"'\"'`)}'`;\n}\n\nfunction normalizeEntryPath(entryPath: string) {\n  return entryPath.startsWith(\"/\") ? entryPath : `/${entryPath}`;\n}\n\nfunction sleep(ms: number) {\n  return new Promise((resolve) => setTimeout(resolve, ms));\n}\n\nfunction buildPreviewLaunchScript({\n  directory,\n  port,\n}: {\n  directory: string;\n  port: number;\n}) {\n  return `exec node ${quoteShell(SANDBOX_AGENT_PREVIEW_SERVER_PATH)} --root ${quoteShell(directory)} --port ${port} >/tmp/sandbox-agent-preview.log 2>&1`;\n}\n\nasync function readPreviewLog(\n  session: Pick<SandboxAgentSession, \"runCommand\">\n) {\n  const logResult = await session.runCommand(\n    \"cat /tmp/sandbox-agent-preview.log 2>/dev/null || true\"\n  );\n\n  return logResult.stdout.trim();\n}\n\nexport async function waitForSandboxPreview({\n  entryPath,\n  fetchPreview = globalThis.fetch,\n  retries = 12,\n  retryDelayMs = 1000,\n  session,\n  url,\n}: {\n  entryPath: string;\n  fetchPreview?: typeof globalThis.fetch;\n  retries?: number;\n  retryDelayMs?: number;\n  session: Pick<SandboxAgentSession, \"runCommand\">;\n  url: string;\n}) {\n  let lastError = \"\";\n\n  for (let attempt = 0; attempt < retries; attempt += 1) {\n    try {\n      const response = await fetchPreview(url, {\n        cache: \"no-store\",\n      });\n\n      if (response.ok) {\n        return;\n      }\n\n      const responseBody = await response.text().catch(() => \"\");\n      lastError = [\n        `HTTP ${response.status} ${response.statusText}`,\n        responseBody.trim(),\n      ]\n        .filter(Boolean)\n        .join(\" \");\n    } catch (error) {\n      lastError =\n        error instanceof Error ? error.message : \"Unknown preview fetch error\";\n    }\n\n    if (attempt < retries - 1) {\n      await sleep(retryDelayMs);\n    }\n  }\n\n  const previewLog = await readPreviewLog(session);\n  const details = [\n    `Preview URL did not become reachable for \"${entryPath}\".`,\n    lastError ? `Last probe error: ${lastError}` : null,\n    previewLog ? `Preview log: ${previewLog}` : null,\n  ]\n    .filter(Boolean)\n    .join(\" \");\n\n  throw new Error(details);\n}\n\nexport async function launchDetachedSandboxPreview({\n  directory,\n  port,\n  sandbox,\n  session,\n}: {\n  directory: string;\n  port: number;\n  sandbox: DetachedPreviewSandboxHandle;\n  session: Pick<SandboxAgentSession, \"runCommand\">;\n}) {\n  await session.runCommand(\n    `pkill -f ${quoteShell(SANDBOX_AGENT_PREVIEW_SERVER_PATH)} >/dev/null 2>&1 || true`\n  );\n\n  await sandbox.runCommand({\n    args: [\"-lc\", buildPreviewLaunchScript({ directory, port })],\n    cmd: \"bash\",\n    cwd: SANDBOX_PROJECT_ROOT,\n    detached: true,\n  });\n}\n\nasync function createVercelBackedSandboxAgentSessionRegistry(\n  env: SandboxAgentEnv\n): Promise<SandboxAgentSessionRegistry> {\n  const {\n    createVercelSandbox,\n    createVercelSandboxSessionRegistry,\n    VERCEL_SANDBOX_WORKSPACE_ROOT,\n  } = await import(\"./vercel-sandbox\");\n  const sandboxHandles = new Map<string, NamedSandboxHandle>();\n  const baseRegistry: BaseSandboxSessionRegistry =\n    createVercelSandboxSessionRegistry({\n      createSandbox: async (sessionId) => {\n        const sandbox = await createVercelSandbox(sessionId, env, {\n          ports: [SANDBOX_AGENT_PREVIEW_PORT],\n        });\n        sandboxHandles.set(\n          sessionId,\n          sandbox as unknown as NamedSandboxHandle\n        );\n        return sandbox;\n      },\n      workspaceRoot: VERCEL_SANDBOX_WORKSPACE_ROOT,\n    });\n\n  const registry: SandboxAgentSessionRegistry = {\n    getDomain(sessionId, port) {\n      const sandbox = sandboxHandles.get(sessionId);\n\n      if (!sandbox) {\n        throw new Error(\n          `Sandbox handle for session \"${sessionId}\" is not available yet.`\n        );\n      }\n\n      return sandbox.domain(port);\n    },\n    getSession(sessionId) {\n      const baseSession = baseRegistry.getSession(sessionId);\n\n      return {\n        artifactsRoot: SANDBOX_ARTIFACTS_ROOT,\n        projectRoot: SANDBOX_PROJECT_ROOT,\n        pathExists: (targetPath) => baseSession.pathExists(targetPath),\n        readFile: (targetPath) => baseSession.readFile(targetPath),\n        runCommand: (command) => baseSession.runCommand(command),\n        sessionId,\n        async startPreview({\n          directory = \".\",\n          entryPath = \"/index.html\",\n          port = SANDBOX_AGENT_PREVIEW_PORT,\n        } = {}) {\n          const directoryExists = await baseSession.pathExists(directory);\n\n          if (!directoryExists) {\n            throw new Error(\n              `Preview directory \"${directory}\" does not exist in the sandbox workspace.`\n            );\n          }\n\n          await baseSession.writeFile(\n            SANDBOX_AGENT_PREVIEW_SERVER_PATH,\n            SANDBOX_AGENT_PREVIEW_SERVER_SOURCE\n          );\n          const sandbox = sandboxHandles.get(sessionId);\n\n          if (!sandbox) {\n            throw new Error(\n              `Sandbox handle for session \"${sessionId}\" is not available when starting the preview server.`\n            );\n          }\n\n          await launchDetachedSandboxPreview({\n            directory,\n            port,\n            sandbox: sandbox as DetachedPreviewSandboxHandle,\n            session: baseSession,\n          });\n\n          const preview = {\n            directory,\n            entryPath: normalizeEntryPath(entryPath),\n            port,\n            url: `${registry.getDomain(sessionId, port)}${normalizeEntryPath(entryPath)}`,\n          };\n\n          await waitForSandboxPreview({\n            entryPath: preview.entryPath,\n            session: baseSession,\n            url: preview.url,\n          });\n\n          return preview;\n        },\n        stop: () => baseSession.stop(),\n        writeFile: (targetPath, content) =>\n          baseSession.writeFile(targetPath, content),\n      };\n    },\n    stopSession(sessionId) {\n      return baseRegistry.stopSession(sessionId);\n    },\n  };\n\n  return registry;\n}\n\nexport async function createSandboxAgentSessionRegistry(\n  env: SandboxAgentEnv = getSandboxAgentEnv(),\n  options: SandboxAgentSessionRegistryOptions = {}\n): Promise<SandboxAgentSessionRegistry> {\n  const setupState = getSandboxAgentSandboxSetupState(env);\n\n  if (setupState.authMode === \"local\") {\n    const { createLocalSandboxAgentSessionRegistry } =\n      await import(\"./local-sandbox\");\n\n    return createLocalSandboxAgentSessionRegistry({\n      localPreviewBaseUrl: options.localPreviewBaseUrl,\n      previewPort: SANDBOX_AGENT_PREVIEW_PORT,\n    });\n  }\n\n  return createVercelBackedSandboxAgentSessionRegistry(env);\n}\n\nlet sharedRegistry: SandboxAgentSessionRegistry | null = null;\nlet sharedRegistryMode:\n  | ReturnType<typeof getSandboxAgentSandboxSetupState>[\"authMode\"]\n  | null = null;\nlet sharedLocalPreviewBaseUrl: string | undefined;\n\nexport async function getSharedSandboxAgentSessionRegistry(\n  env: SandboxAgentEnv = getSandboxAgentEnv(),\n  options: SandboxAgentSessionRegistryOptions = {}\n) {\n  const setupState = getSandboxAgentSandboxSetupState(env);\n\n  if (options.localPreviewBaseUrl) {\n    sharedLocalPreviewBaseUrl = options.localPreviewBaseUrl;\n  }\n\n  if (!sharedRegistry || sharedRegistryMode !== setupState.authMode) {\n    sharedRegistry = await createSandboxAgentSessionRegistry(env, {\n      localPreviewBaseUrl: sharedLocalPreviewBaseUrl,\n    });\n    sharedRegistryMode = setupState.authMode;\n  }\n\n  return sharedRegistry;\n}\n",
      "type": "registry:lib",
      "target": "@lib/sandbox-agent/server/session.ts"
    },
    {
      "path": "registry/sandbox-agent/lib/sandbox-agent/server/vercel-sandbox.ts",
      "content": "import { readdir, readFile, stat } from \"node:fs/promises\";\nimport path, { posix as posixPath } from \"node:path\";\nimport { Sandbox } from \"@vercel/sandbox\";\nimport {\n  getSandboxAgentEnv,\n  getSandboxAgentSandboxSetupState,\n  getSandboxAgentSandboxTokenCredentials,\n  type SandboxAgentEnv,\n} from \"./env\";\n\nexport const VERCEL_SANDBOX_WORKSPACE_ROOT = process.cwd();\nexport const VERCEL_SANDBOX_PROJECT_ROOT = \"/vercel/sandbox/project\";\nexport const VERCEL_SANDBOX_SKILLS_ROOT =\n  `${VERCEL_SANDBOX_PROJECT_ROOT}/.agents/skills`;\nexport const VERCEL_SANDBOX_ARTIFACTS_ROOT =\n  `${VERCEL_SANDBOX_PROJECT_ROOT}/artifacts`;\nexport const VERCEL_SANDBOX_AGENTS_FILE =\n  `${VERCEL_SANDBOX_PROJECT_ROOT}/AGENTS.md`;\n\ninterface SandboxFs {\n  exists(path: string): Promise<boolean>;\n  mkdir(path: string, options?: { recursive?: boolean }): Promise<void>;\n  readFile(path: string, encoding: BufferEncoding | \"utf-8\"): Promise<string>;\n  writeFile(\n    path: string,\n    content: string | Uint8Array,\n    encoding?: BufferEncoding | \"utf-8\"\n  ): Promise<void>;\n}\n\nexport interface VercelSandboxHandle {\n  fs: SandboxFs;\n  runCommand(options: { args: string[]; cmd: string; cwd: string }): Promise<{\n    exitCode: number;\n    stderr(): Promise<string>;\n    stdout(): Promise<string>;\n  }>;\n  stop(): Promise<unknown>;\n}\n\nexport type VercelSandboxFactory = (\n  sessionId: string\n) => Promise<VercelSandboxHandle>;\n\nexport interface VercelSandboxSession {\n  readonly artifactsRoot: string;\n  pathExists(targetPath: string): Promise<boolean>;\n  readonly projectRoot: string;\n  readFile(targetPath: string): Promise<string>;\n  runCommand(command: string): Promise<{\n    command: string;\n    exitCode: number;\n    stderr: string;\n    stdout: string;\n  }>;\n  readonly sessionId: string;\n  stop(): Promise<void>;\n  writeFile(\n    targetPath: string,\n    content: string\n  ): Promise<{\n    bytes: number;\n    path: string;\n  }>;\n}\n\nexport interface VercelSandboxSessionRegistry {\n  getSandbox(sessionId: string): Promise<VercelSandboxHandle>;\n  getSession(sessionId: string): VercelSandboxSession;\n  stopSession(sessionId: string): Promise<void>;\n}\n\ninterface SessionEntry {\n  sandboxPromise: Promise<VercelSandboxHandle> | null;\n  seededBaseWorkspace: boolean;\n}\n\nexport async function createVercelSandbox(\n  sessionId: string,\n  env: SandboxAgentEnv = getSandboxAgentEnv(),\n  options: {\n    ports?: number[];\n  } = {}\n): Promise<VercelSandboxHandle> {\n  const setupState = getSandboxAgentSandboxSetupState(env);\n\n  if (!setupState.isReady) {\n    throw new Error(setupState.issues.join(\" \"));\n  }\n\n  const baseOptions = {\n    name: sessionId,\n    persistent: true,\n    ports: options.ports,\n    runtime: setupState.runtime,\n    timeout: 300_000,\n  };\n\n  if (setupState.authMode === \"oidc\") {\n    try {\n      return (await Sandbox.get({\n        name: sessionId,\n      })) as unknown as VercelSandboxHandle;\n    } catch {\n      return (await Sandbox.create(\n        baseOptions\n      )) as unknown as VercelSandboxHandle;\n    }\n  }\n\n  const tokenCredentials = getSandboxAgentSandboxTokenCredentials(env);\n\n  try {\n    return (await Sandbox.get({\n      name: sessionId,\n      projectId: tokenCredentials.projectId,\n      teamId: tokenCredentials.teamId,\n      token: tokenCredentials.token,\n    })) as unknown as VercelSandboxHandle;\n  } catch {\n    return (await Sandbox.create({\n      ...baseOptions,\n      projectId: tokenCredentials.projectId,\n      teamId: tokenCredentials.teamId,\n      token: tokenCredentials.token,\n    })) as unknown as VercelSandboxHandle;\n  }\n}\n\nasync function pathExists(localPath: string) {\n  try {\n    await stat(localPath);\n    return true;\n  } catch (error) {\n    if (\n      typeof error === \"object\" &&\n      error !== null &&\n      \"code\" in error &&\n      error.code === \"ENOENT\"\n    ) {\n      return false;\n    }\n\n    throw error;\n  }\n}\n\nexport async function copyLocalPathToSandbox(\n  sandbox: VercelSandboxHandle,\n  localPath: string,\n  remotePath: string\n) {\n  const entry = await stat(localPath);\n\n  if (entry.isDirectory()) {\n    await sandbox.fs.mkdir(remotePath, { recursive: true });\n\n    for (const child of await readdir(localPath)) {\n      await copyLocalPathToSandbox(\n        sandbox,\n        path.join(localPath, child),\n        posixPath.join(remotePath, child)\n      );\n    }\n\n    return;\n  }\n\n  await sandbox.fs.mkdir(posixPath.dirname(remotePath), { recursive: true });\n  await sandbox.fs.writeFile(remotePath, await readFile(localPath));\n}\n\nfunction resolveSandboxPath(targetPath: string) {\n  if (targetPath.startsWith(\"/\")) {\n    return targetPath;\n  }\n\n  return posixPath.join(VERCEL_SANDBOX_PROJECT_ROOT, targetPath);\n}\n\nfunction shellQuote(value: string) {\n  return `'${value.replace(/'/g, `'\"'\"'`)}'`;\n}\n\nfunction formatSandboxError(error: unknown) {\n  if (!(error instanceof Error)) {\n    return String(error);\n  }\n\n  const details = [\n    \"response\" in error &&\n    typeof error.response === \"object\" &&\n    error.response &&\n    \"status\" in error.response &&\n    typeof error.response.status === \"number\"\n      ? `HTTP ${error.response.status}`\n      : null,\n    \"sandboxName\" in error && typeof error.sandboxName === \"string\"\n      ? `sandbox=${error.sandboxName}`\n      : null,\n    \"sessionId\" in error && typeof error.sessionId === \"string\"\n      ? `session=${error.sessionId}`\n      : null,\n    \"json\" in error && typeof error.json === \"object\" && error.json\n      ? `json=${JSON.stringify(error.json)}`\n      : null,\n    \"text\" in error && typeof error.text === \"string\" && error.text\n      ? `text=${error.text}`\n      : null,\n    error.message ? `message=${error.message}` : null,\n  ].filter(Boolean);\n\n  return details.join(\" | \") || error.message;\n}\n\nfunction createSandboxOperationError({\n  action,\n  error,\n  target,\n}: {\n  action: string;\n  error: unknown;\n  target: string;\n}) {\n  return new Error(\n    `Sandbox ${action} failed for ${target}. ${formatSandboxError(error)}`\n  );\n}\n\nasync function writeSandboxFile(\n  sandbox: VercelSandboxHandle,\n  resolvedPath: string,\n  content: string\n) {\n  try {\n    await sandbox.fs.mkdir(posixPath.dirname(resolvedPath), {\n      recursive: true,\n    });\n    await sandbox.fs.writeFile(resolvedPath, content, \"utf-8\");\n    return;\n  } catch (primaryError) {\n    const base64Content = Buffer.from(content, \"utf-8\").toString(\"base64\");\n    const fallbackCommand = [\n      `mkdir -p ${shellQuote(posixPath.dirname(resolvedPath))}`,\n      `base64 -d > ${shellQuote(resolvedPath)} <<'__SKILLS_AGENT_CONTENT__'`,\n      base64Content,\n      \"__SKILLS_AGENT_CONTENT__\",\n    ].join(\"\\n\");\n\n    let fallbackResult: Awaited<ReturnType<VercelSandboxHandle[\"runCommand\"]>>;\n\n    try {\n      fallbackResult = await sandbox.runCommand({\n        args: [\"-lc\", fallbackCommand],\n        cmd: \"bash\",\n        cwd: VERCEL_SANDBOX_PROJECT_ROOT,\n      });\n    } catch (fallbackError) {\n      throw new Error(\n        [\n          `Sandbox writeFile failed for ${resolvedPath}.`,\n          `File API error: ${formatSandboxError(primaryError)}`,\n          `Shell fallback error: ${formatSandboxError(fallbackError)}`,\n        ].join(\" \")\n      );\n    }\n\n    if (fallbackResult.exitCode === 0) {\n      return;\n    }\n\n    throw new Error(\n      [\n        \"Failed to write a sandbox file.\",\n        `File API error: ${formatSandboxError(primaryError)}`,\n        `Shell fallback error: ${(await fallbackResult.stderr()).trim() || \"unknown error\"}`,\n      ].join(\" \")\n    );\n  }\n}\n\nexport function createVercelSandboxSessionRegistry({\n  createSandbox,\n  workspaceRoot = VERCEL_SANDBOX_WORKSPACE_ROOT,\n}: {\n  createSandbox: VercelSandboxFactory;\n  workspaceRoot?: string;\n}): VercelSandboxSessionRegistry {\n  const entries = new Map<string, SessionEntry>();\n\n  const stopSession = async (sessionId: string) => {\n    const entry = entries.get(sessionId);\n\n    if (!entry) {\n      return;\n    }\n\n    if (!entry.sandboxPromise) {\n      entries.delete(sessionId);\n      return;\n    }\n\n    let sandbox: VercelSandboxHandle;\n\n    try {\n      sandbox = await entry.sandboxPromise;\n    } catch {\n      entries.delete(sessionId);\n      return;\n    }\n\n    await sandbox.stop();\n    entries.delete(sessionId);\n  };\n\n  const getOrCreateEntry = (sessionId: string) => {\n    const existingEntry = entries.get(sessionId);\n\n    if (existingEntry) {\n      return existingEntry;\n    }\n\n    const createdEntry: SessionEntry = {\n      sandboxPromise: null,\n      seededBaseWorkspace: false,\n    };\n    entries.set(sessionId, createdEntry);\n    return createdEntry;\n  };\n\n  const ensureSandbox = async (sessionId: string, entry: SessionEntry) => {\n    if (!entry.sandboxPromise) {\n      entry.sandboxPromise = (async () => {\n        let lastError: unknown;\n\n        for (let attempt = 0; attempt < 2; attempt += 1) {\n          try {\n            const sandbox = await createSandbox(sessionId);\n\n            if (!entry.seededBaseWorkspace) {\n              await sandbox.fs.mkdir(VERCEL_SANDBOX_PROJECT_ROOT, {\n                recursive: true,\n              });\n              await sandbox.fs.mkdir(VERCEL_SANDBOX_SKILLS_ROOT, {\n                recursive: true,\n              });\n              await sandbox.fs.mkdir(VERCEL_SANDBOX_ARTIFACTS_ROOT, {\n                recursive: true,\n              });\n\n              const agentsPath = path.join(\n                /*turbopackIgnore: true*/ workspaceRoot,\n                \"AGENTS.md\"\n              );\n\n              if (await pathExists(agentsPath)) {\n                await copyLocalPathToSandbox(\n                  sandbox,\n                  agentsPath,\n                  VERCEL_SANDBOX_AGENTS_FILE\n                );\n              }\n\n              entry.seededBaseWorkspace = true;\n            }\n\n            return sandbox;\n          } catch (error) {\n            lastError = error;\n            entry.seededBaseWorkspace = false;\n          }\n        }\n\n        throw lastError;\n      })().catch((error) => {\n        entry.sandboxPromise = null;\n        throw error;\n      });\n    }\n\n    return await entry.sandboxPromise;\n  };\n\n  return {\n    getSandbox(sessionId) {\n      const entry = getOrCreateEntry(sessionId);\n      return ensureSandbox(sessionId, entry);\n    },\n    getSession(sessionId) {\n      const entry = getOrCreateEntry(sessionId);\n\n      return {\n        artifactsRoot: VERCEL_SANDBOX_ARTIFACTS_ROOT,\n        projectRoot: VERCEL_SANDBOX_PROJECT_ROOT,\n        sessionId,\n        async pathExists(targetPath) {\n          const sandbox = await ensureSandbox(sessionId, entry);\n          return sandbox.fs.exists(resolveSandboxPath(targetPath));\n        },\n        async readFile(targetPath) {\n          const sandbox = await ensureSandbox(sessionId, entry);\n          const resolvedPath = resolveSandboxPath(targetPath);\n\n          try {\n            return await sandbox.fs.readFile(resolvedPath, \"utf-8\");\n          } catch (error) {\n            throw createSandboxOperationError({\n              action: \"readFile\",\n              error,\n              target: resolvedPath,\n            });\n          }\n        },\n        async runCommand(command) {\n          const sandbox = await ensureSandbox(sessionId, entry);\n          let result: Awaited<ReturnType<VercelSandboxHandle[\"runCommand\"]>>;\n\n          try {\n            result = await sandbox.runCommand({\n              args: [\"-lc\", command],\n              cmd: \"bash\",\n              cwd: VERCEL_SANDBOX_PROJECT_ROOT,\n            });\n          } catch (error) {\n            throw createSandboxOperationError({\n              action: \"bash\",\n              error,\n              target: command,\n            });\n          }\n\n          return {\n            command,\n            exitCode: result.exitCode,\n            stderr: await result.stderr(),\n            stdout: await result.stdout(),\n          };\n        },\n        async stop() {\n          await stopSession(sessionId);\n        },\n        async writeFile(targetPath, content) {\n          const sandbox = await ensureSandbox(sessionId, entry);\n          const resolvedPath = resolveSandboxPath(targetPath);\n\n          await writeSandboxFile(sandbox, resolvedPath, content);\n\n          return {\n            bytes: Buffer.byteLength(content, \"utf-8\"),\n            path: resolvedPath,\n          };\n        },\n      };\n    },\n    stopSession,\n  };\n}\n\nlet sharedRegistry: VercelSandboxSessionRegistry | null = null;\n\nexport function getSharedVercelSandboxSessionRegistry(\n  env: SandboxAgentEnv = getSandboxAgentEnv()\n) {\n  sharedRegistry ??= createVercelSandboxSessionRegistry({\n    createSandbox: (sessionId) => createVercelSandbox(sessionId, env),\n  });\n\n  return sharedRegistry;\n}\n",
      "type": "registry:lib",
      "target": "@lib/sandbox-agent/server/vercel-sandbox.ts"
    },
    {
      "path": "registry/sandbox-agent/lib/sandbox-agent/server/workspace.ts",
      "content": "import {\n  createSandboxAgentToolset,\n  type SandboxAgentToolset,\n} from \"./official-tools\";\nimport {\n  getSharedSandboxAgentSessionRegistry,\n  SANDBOX_AGENT_PREVIEW_PORT,\n  type SandboxAgentSession,\n} from \"./session\";\nimport { getSandboxAgentEnv, type SandboxAgentEnv } from \"./env\";\n\nexport const defaultSandboxAgentSuggestedUseCases = [\n  \"Build a pricing landing page with an interactive calculator and start a live preview.\",\n  \"Create a product microsite with HTML, CSS, and JavaScript, then expose the result through the sandbox preview.\",\n  \"Iterate on a generated landing page, update the copy and calculator logic, and refresh the same sandbox preview.\",\n] as const;\n\nexport interface SandboxAgentWorkspace {\n  readonly artifactsRoot: string;\n  readonly previewPort: number;\n  readonly projectRoot: string;\n  readonly session: SandboxAgentSession;\n  readonly suggestedUseCases: readonly string[];\n  readonly suggestedUseCasesText: string;\n  readonly toolset: SandboxAgentToolset;\n}\n\nexport function formatSuggestedUseCases(useCases: readonly string[]) {\n  return useCases.map((useCase) => `- ${useCase}`).join(\"\\n\");\n}\n\nexport async function createSandboxAgentWorkspace(\n  {\n    env = getSandboxAgentEnv(),\n    localPreviewBaseUrl,\n    sessionId,\n    suggestedUseCases = defaultSandboxAgentSuggestedUseCases,\n  }: {\n    env?: SandboxAgentEnv;\n    localPreviewBaseUrl?: string;\n    sessionId: string;\n    suggestedUseCases?: readonly string[];\n  },\n  dependencies: {\n    createToolset?: typeof createSandboxAgentToolset;\n    getSession?: (sessionId: string) => SandboxAgentSession;\n  } = {}\n): Promise<SandboxAgentWorkspace> {\n  const registry = await getSharedSandboxAgentSessionRegistry(env, {\n    localPreviewBaseUrl,\n  });\n  const getSession =\n    dependencies.getSession ?? registry.getSession.bind(registry);\n  const session = getSession(sessionId);\n  const createToolset = dependencies.createToolset ?? createSandboxAgentToolset;\n  const toolset = await createToolset({\n    projectRoot: session.projectRoot,\n    session,\n  });\n\n  return {\n    artifactsRoot: session.artifactsRoot,\n    previewPort: SANDBOX_AGENT_PREVIEW_PORT,\n    projectRoot: session.projectRoot,\n    session,\n    suggestedUseCases,\n    suggestedUseCasesText: formatSuggestedUseCases(suggestedUseCases),\n    toolset,\n  };\n}\n",
      "type": "registry:lib",
      "target": "@lib/sandbox-agent/server/workspace.ts"
    }
  ],
  "envVars": {
    "AI_GATEWAY_API_KEY": "",
    "AI_GATEWAY_BASE_URL": "https://ai-gateway.vercel.sh/v3/ai",
    "AI_GATEWAY_CHAT_MODEL": "openai/gpt-5-mini"
  },
  "docs": "Install into a Next.js App Router project initialized with shadcn/ui. This item adds the sandbox-agent page, node runtime chat route, local preview route, preview health route, client workspace, sandbox session registry, shell/file tool surface, and AI Gateway env vars. It runs with a local isolated workspace by default; Vercel Sandbox credentials are optional if you want remote sandbox execution.",
  "type": "registry:block"
}
