import { createRoot } from "react-dom/client";
import { useAgent } from "agents/react";
import { useAgentChat } from "@cloudflare/ai-chat/react";
import { isToolUIPart } from "ai";
import "./styles.css";
import { useState, useEffect, useRef, useCallback } from "react";
import { Streamdown } from "streamdown";
import {
Button,
Surface,
Text,
InputArea,
Empty,
Badge
} from "@cloudflare/kumo";
import {
PaperPlaneRightIcon,
TrashIcon,
GearIcon,
LightningIcon,
CaretRightIcon,
XIcon,
CodeIcon,
TerminalIcon,
WarningCircleIcon,
CheckCircleIcon,
CircleNotchIcon,
BrainIcon,
CaretDownIcon,
PlugsConnectedIcon,
PlusIcon,
CubeIcon,
SpinnerGapIcon
} from "@phosphor-icons/react";
import {
ModeToggle,
PoweredByAgents,
ConnectionIndicator,
type ConnectionStatus
} from "@cloudflare/agents-ui";
import { ThemeProvider } from "@cloudflare/agents-ui/hooks";
import type { ExecutorType } from "./server";
interface McpTool {
serverId: string;
name: string;
description?: string;
}
interface ToolPart {
type: string;
toolCallId?: string;
state?: string;
errorText?: string;
input?: { functionDescription?: string; [key: string]: unknown };
output?: {
code?: string;
result?: string;
logs?: string[];
[key: string]: unknown;
};
}
const EXECUTORS: { value: ExecutorType; label: string; description: string }[] =
[
{
value: "dynamic-worker",
label: "Dynamic Worker",
description: "Sandboxed Cloudflare Worker via WorkerLoader"
},
{
value: "node-server",
label: "Node Server",
description: "Node.js VM via external HTTP server"
}
];
const TOOLS: { name: string; description: string }[] = [
{ name: "createProject", description: "Create a new project" },
{ name: "listProjects", description: "List all projects" },
{ name: "createTask", description: "Create a task in a project" },
{ name: "listTasks", description: "List tasks with optional filters" },
{ name: "updateTask", description: "Update a task's fields" },
{ name: "deleteTask", description: "Delete a task and its comments" },
{ name: "createSprint", description: "Create a sprint for a project" },
{ name: "listSprints", description: "List sprints, optionally by project" },
{ name: "addComment", description: "Add a comment to a task" },
{ name: "listComments", description: "List comments on a task" }
];
function extractFunctionCalls(code?: string): string[] {
if (!code) return [];
const matches = code.match(/codemode\.(\w+)/g);
if (!matches) return [];
return [...new Set(matches.map((m) => m.replace("codemode.", "")))];
}
function ReasoningBlock({
text,
isStreaming
}: {
text: string;
isStreaming: boolean;
}) {
const [expanded, setExpanded] = useState(false);
if (!text?.trim()) return null;
return (
setExpanded(!expanded)}
className="w-full flex items-center gap-2 px-3 py-2 cursor-pointer"
>
Thinking
{expanded && (
{text}
)}
);
}
function ToolCard({ toolPart }: { toolPart: ToolPart }) {
const [expanded, setExpanded] = useState(false);
const hasError = toolPart.state === "output-error" || !!toolPart.errorText;
const isComplete = toolPart.state === "output-available";
const isRunning = !isComplete && !hasError;
const functionCalls = extractFunctionCalls(
toolPart.output?.code || (toolPart.input?.code as string)
);
const summary =
functionCalls.length > 0 ? functionCalls.join(", ") : "code execution";
return (
setExpanded(!expanded)}
>
Ran code
{functionCalls.length > 0 && (
<>
·
{summary}
>
)}
{isComplete && (
)}
{hasError && (
)}
{isRunning && (
)}
{expanded && (
{toolPart.output?.code && (
Code
{toolPart.output.code}
)}
{!toolPart.output?.code && toolPart.input && (
Input
{JSON.stringify(toolPart.input, null, 2)}
)}
{toolPart.output?.result !== undefined && (
Result
{JSON.stringify(toolPart.output.result, null, 2)}
)}
{toolPart.output?.logs && toolPart.output.logs.length > 0 && (
Console
{toolPart.output.logs.join("\n")}
)}
{toolPart.errorText && (
Error
{toolPart.errorText}
)}
)}
);
}
function SettingsPanel({
executor,
onExecutorChange,
loading,
onClose,
mcpTools,
onAddMcp,
onRemoveMcp,
onRefreshMcpTools,
mcpLoading
}: {
executor: ExecutorType;
onExecutorChange: (e: ExecutorType) => void;
loading: boolean;
onClose: () => void;
mcpTools: McpTool[];
onAddMcp: (url: string, name?: string) => Promise;
onRemoveMcp: (serverId: string) => Promise;
onRefreshMcpTools: () => void;
mcpLoading: boolean;
}) {
const [mcpUrl, setMcpUrl] = useState("");
const [mcpName, setMcpName] = useState("");
const [addingMcp, setAddingMcp] = useState(false);
const handleAddMcp = async () => {
if (!mcpUrl.trim()) return;
setAddingMcp(true);
try {
await onAddMcp(mcpUrl.trim(), mcpName.trim() || undefined);
setMcpUrl("");
setMcpName("");
} finally {
setAddingMcp(false);
}
};
// Group MCP tools by server
const toolsByServer = mcpTools.reduce(
(acc, tool) => {
if (!acc[tool.serverId]) acc[tool.serverId] = [];
acc[tool.serverId].push(tool);
return acc;
},
{} as Record
);
return (
<>
Settings
}
onClick={onClose}
aria-label="Close"
/>
{/* Executor Section */}
Executor
onExecutorChange(e.target.value as ExecutorType)}
disabled={loading}
>
{EXECUTORS.map((exec) => (
{exec.label}
))}
{EXECUTORS.find((e) => e.value === executor)?.description}
{/* MCP Servers Section */}
setMcpUrl(e.target.value)}
placeholder="https://docs.mcp.cloudflare.com/mcp"
className="w-full px-3 py-2.5 bg-kumo-base border border-kumo-line rounded-lg text-kumo-default text-sm outline-none focus:ring-2 focus:ring-orange-500/30 focus:border-orange-500/50 transition-all placeholder:text-kumo-inactive"
disabled={addingMcp}
/>
setMcpName(e.target.value)}
placeholder="Server name (optional)"
className="w-full px-3 py-2 bg-kumo-base border border-kumo-line rounded-lg text-kumo-default text-xs outline-none focus:ring-2 focus:ring-orange-500/30 focus:border-orange-500/50 transition-all placeholder:text-kumo-inactive"
disabled={addingMcp}
/>
}
className="w-full !bg-gradient-to-r !from-orange-500/10 !to-amber-500/10 hover:!from-orange-500/20 hover:!to-amber-500/20 !border-orange-500/30"
>
Connect MCP Server
{Object.keys(toolsByServer).length > 0 && (
Connected Servers
{mcpLoading ? (
) : (
"Refresh"
)}
{Object.entries(toolsByServer).map(([serverId, tools]) => (
{serverId}
{tools.length} tools
onRemoveMcp(serverId)}
className="p-1 text-kumo-inactive hover:text-red-500 transition-colors"
title="Remove server"
>
{tools.map((tool) => (
{tool.name}
{tool.description && (
{tool.description}
)}
))}
))}
)}
{Object.keys(toolsByServer).length === 0 && (
)}
{/* Available Functions Section */}
Built-in Functions
{TOOLS.map((tool) => (
{tool.name}
{tool.description}
))}
>
);
}
function App() {
const [input, setInput] = useState("");
const [executor, setExecutor] = useState("dynamic-worker");
const [settingsOpen, setSettingsOpen] = useState(false);
const [connectionStatus, setConnectionStatus] =
useState("connecting");
const [mcpTools, setMcpTools] = useState([]);
const [mcpLoading, setMcpLoading] = useState(false);
const messagesEndRef = useRef(null);
const agent = useAgent({
agent: "codemode",
onOpen: useCallback(() => setConnectionStatus("connected"), []),
onClose: useCallback(() => setConnectionStatus("disconnected"), []),
onError: useCallback(() => setConnectionStatus("disconnected"), [])
});
const { messages, sendMessage, clearHistory, status } = useAgentChat({
agent
});
const isStreaming = status === "streaming";
const isConnected = connectionStatus === "connected";
const handleExecutorChange = useCallback(
(newExecutor: ExecutorType) => {
setExecutor(newExecutor);
agent.call("setExecutor", [newExecutor]);
},
[agent]
);
const refreshMcpTools = useCallback(async () => {
setMcpLoading(true);
try {
const tools = await agent.call("listMcpTools", []);
setMcpTools(tools as McpTool[]);
} catch (err) {
console.error("Failed to list MCP tools:", err);
} finally {
setMcpLoading(false);
}
}, [agent]);
const handleAddMcp = useCallback(
async (url: string, name?: string) => {
try {
await agent.call("addMcp", [url, name]);
// Refresh tools list after adding server
await refreshMcpTools();
} catch (err) {
console.error("Failed to add MCP server:", err);
throw err;
}
},
[agent, refreshMcpTools]
);
const handleRemoveMcp = useCallback(
async (serverId: string) => {
try {
await agent.call("removeMcp", [serverId]);
await refreshMcpTools();
} catch (err) {
console.error("Failed to remove MCP server:", err);
}
},
[agent, refreshMcpTools]
);
// Load MCP tools when settings panel opens
useEffect(() => {
if (settingsOpen && isConnected) {
refreshMcpTools();
}
}, [settingsOpen, isConnected, refreshMcpTools]);
const send = useCallback(() => {
const text = input.trim();
if (!text || isStreaming) return;
setInput("");
sendMessage({ role: "user", parts: [{ type: "text", text }] });
}, [input, isStreaming, sendMessage]);
useEffect(() => {
messagesEndRef.current?.scrollIntoView({ behavior: "smooth" });
}, [messages]);
return (
{messages.length === 0 && (
}
title="Welcome to Codemode"
description="AI-powered project management. Ask me to create projects, manage tasks, plan sprints, and more."
/>
)}
{messages.map((message, msgIndex) => {
const isUser = message.role === "user";
const isLastAssistant =
message.role === "assistant" && msgIndex === messages.length - 1;
const isAnimating = isStreaming && isLastAssistant;
if (isUser) {
return (
{message.parts
.filter((p) => p.type === "text")
.map((p) => (p.type === "text" ? p.text : ""))
.join("")}
);
}
return (
{message.parts.map((part, partIdx) => {
if (part.type === "text") {
if (!part.text || part.text.trim() === "") return null;
return (
{part.text}
);
}
if (part.type === "step-start") return null;
if (part.type === "reasoning") {
return (
);
}
if (isToolUIPart(part)) {
const toolPart = part as unknown as ToolPart;
return (
);
}
return null;
})}
);
})}
{settingsOpen && (
setSettingsOpen(false)}
mcpTools={mcpTools}
onAddMcp={handleAddMcp}
onRemoveMcp={handleRemoveMcp}
onRefreshMcpTools={refreshMcpTools}
mcpLoading={mcpLoading}
/>
)}
);
}
createRoot(document.getElementById("root")!).render(
);