"use client"; import { useState, useEffect, type ReactNode } from "react"; import { defineToolCallRenderer } from "@copilotkit/react-core/v2"; import { z } from "zod"; import { triggerBlobDownload } from "@/lib/open-download"; // ── Download link for restart_server ───────────────────────────────────────── function DownloadServerCodeLink({ workspaceId }: { workspaceId: string }) { const [loading, setLoading] = useState(false); const [error, setError] = useState(null); const handleDownload = async () => { setError(null); setLoading(true); try { const res = await fetch("/api/workspace/download", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ workspaceId, stream: true, fullKit: true }), }); if (!res.ok) { const data = (await res.json().catch(() => ({}))) as { error?: string }; throw new Error(data.error || `Download failed (${res.status})`); } const blob = await res.blob(); const safeId = workspaceId.replace(/[^\w-]/g, "").slice(0, 16) || "workspace"; const cd = res.headers.get("Content-Disposition"); const m = cd?.match(/filename="([^"]+)"/); const filename = m?.[1] ?? `workspace-${safeId}.tar.gz`; triggerBlobDownload(blob, filename); } catch (e) { setError(e instanceof Error ? e.message : "Download failed"); } finally { setLoading(false); } }; return (

Local starter kit

{error &&

{error}

}
); } // ── Helpers ────────────────────────────────────────────────────────────────── function safeFormat(val: unknown, maxLen = 800): string { if (val === null || val === undefined) return ""; let s: string; try { s = typeof val === "string" ? val : JSON.stringify(val, null, 2); } catch { s = String(val); } return s.length > maxLen ? s.slice(0, maxLen) + "\n…(truncated)" : s; } // ── Card component ──────────────────────────────────────────────────────────── function ToolCallCard({ name, args, status, result, footer, }: { name: string; args: unknown; status: string; result: string | undefined; footer?: ReactNode; }) { const done = status === "complete"; // Start expanded while running; auto-collapse when done. // User can click the header to toggle at any time. const [expanded, setExpanded] = useState(true); useEffect(() => { if (done) setExpanded(false); }, [done]); // Build a human-readable args string const argsEntries = args && typeof args === "object" && !Array.isArray(args) ? Object.entries(args as Record) : null; const hasArgs = argsEntries ? argsEntries.length > 0 : args != null; const argsDisplay = argsEntries ? argsEntries.map(([k, v]) => `${k}: ${safeFormat(v, 300)}`).join("\n") : safeFormat(args, 600); const resultDisplay = result !== undefined ? safeFormat(result, 800) : null; return (
{/* ── Header (always visible, clickable) ── */} {/* ── Expandable body ── */} {expanded && (
{/* Arguments */} {hasArgs && (

Arguments

                {argsDisplay}
              
)} {/* Result */} {resultDisplay !== null && (

Result

                {resultDisplay}
              
)}
)} {footer}
); } // ── restart_server: same card + download MCP server code link ────────────────── function RestartServerCard({ name, args, status, result, }: { name: string; args: unknown; status: string; result: string | undefined; }) { const workspaceId = args && typeof args === "object" && !Array.isArray(args) && "workspaceId" in args ? String((args as Record).workspaceId) : null; return ( ) : null } /> ); } // ── Wildcard renderer — catches all tool calls with no specific renderer ────── // Module-level constant so it's stable across renders (no new array each time). export const TOOL_CALL_RENDERERS = [ defineToolCallRenderer({ name: "restart_server", args: z.object({ workspaceId: z.string() }), render: ({ name, args, status, result }) => ( ), }), defineToolCallRenderer({ name: "*", render: ({ name, args, status, result }) => ( ), }), ];